Compare commits

..

1 Commits
master ... plyr

Author SHA1 Message Date
Thomas Forgione 0201bcd4a9 First attempt with plyr 2021-05-29 15:38:59 +02:00
13 changed files with 2450 additions and 131 deletions

4
.gitignore vendored
View File

@ -1,4 +1,6 @@
videos videos
elm-stuff elm-stuff
js js/main.js
js/main.tmp.js
js/main.min.js
deploy.sh deploy.sh

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "elm-video"]
path = elm-video
url = https://github.com/polymny/elm-video

View File

@ -18,13 +18,13 @@ dev: js/main.js
release: js/main.min.js release: js/main.min.js
js/main.js: src/** elm-video/src/** js/main.js: src/**
$(ELM) make src/Main.elm --output $(BUILD_DIR)/main.js $(ELM) make src/Main.elm --output $(BUILD_DIR)/main.js
js/main.min.js: js/main.tmp.js js/main.min.js: js/main.tmp.js
@$(UGLIFYJS) $(BUILD_DIR)/main.tmp.js --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' | uglifyjs --mangle > $(BUILD_DIR)/main.min.js @$(UGLIFYJS) $(BUILD_DIR)/main.tmp.js --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' | uglifyjs --mangle > $(BUILD_DIR)/main.min.js
js/main.tmp.js: src/** elm-video/src/** js/main.tmp.js: src/**
@$(ELM) make src/Main.elm --optimize --output $(BUILD_DIR)/main.tmp.js @$(ELM) make src/Main.elm --optimize --output $(BUILD_DIR)/main.tmp.js
watch: watch:

78
css/spinner.css Normal file
View File

@ -0,0 +1,78 @@
.lds-spinner {
color: official;
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-spinner div {
transform-origin: 40px 40px;
animation: lds-spinner 1.2s linear infinite;
}
.lds-spinner div:after {
content: " ";
display: block;
position: absolute;
top: 3px;
left: 37px;
width: 6px;
height: 18px;
border-radius: 20%;
background: #cef;
}
.lds-spinner div:nth-child(1) {
transform: rotate(0deg);
animation-delay: -1.1s;
}
.lds-spinner div:nth-child(2) {
transform: rotate(30deg);
animation-delay: -1s;
}
.lds-spinner div:nth-child(3) {
transform: rotate(60deg);
animation-delay: -0.9s;
}
.lds-spinner div:nth-child(4) {
transform: rotate(90deg);
animation-delay: -0.8s;
}
.lds-spinner div:nth-child(5) {
transform: rotate(120deg);
animation-delay: -0.7s;
}
.lds-spinner div:nth-child(6) {
transform: rotate(150deg);
animation-delay: -0.6s;
}
.lds-spinner div:nth-child(7) {
transform: rotate(180deg);
animation-delay: -0.5s;
}
.lds-spinner div:nth-child(8) {
transform: rotate(210deg);
animation-delay: -0.4s;
}
.lds-spinner div:nth-child(9) {
transform: rotate(240deg);
animation-delay: -0.3s;
}
.lds-spinner div:nth-child(10) {
transform: rotate(270deg);
animation-delay: -0.2s;
}
.lds-spinner div:nth-child(11) {
transform: rotate(300deg);
animation-delay: -0.1s;
}
.lds-spinner div:nth-child(12) {
transform: rotate(330deg);
animation-delay: 0s;
}
@keyframes lds-spinner {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}

1664
css/video-js.css Normal file

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
Subproject commit 087c3d594b62b6617954fc31a58576f12752d75a

View File

@ -1,29 +1,25 @@
{ {
"type": "application", "type": "application",
"source-directories": [ "source-directories": [
"src", "src"
"elm-video/src"
], ],
"elm-version": "0.19.1", "elm-version": "0.19.1",
"dependencies": { "dependencies": {
"direct": { "direct": {
"andrewMacmurray/elm-simple-animation": "2.1.0", "STTR13/ziplist": "1.3.0",
"elm/browser": "1.0.2", "elm/browser": "1.0.2",
"elm/core": "1.0.5", "elm/core": "1.0.5",
"elm/html": "1.0.0", "elm/html": "1.0.0",
"elm/http": "2.0.0", "elm/http": "2.0.0",
"elm/json": "1.1.3", "elm/json": "1.1.3",
"elm/svg": "1.0.1",
"elm/time": "1.0.0", "elm/time": "1.0.0",
"elm/url": "1.0.0", "elm/url": "1.0.0",
"icidasset/elm-material-icons": "9.0.0",
"jims/html-parser": "1.0.0", "jims/html-parser": "1.0.0",
"justinmimbs/timezone-data": "3.0.3", "justinmimbs/timezone-data": "3.0.3",
"mdgriffith/elm-ui": "1.1.8", "mdgriffith/elm-ui": "1.1.8",
"rtfeldman/elm-iso8601-date-strings": "1.1.3" "rtfeldman/elm-iso8601-date-strings": "1.1.3"
}, },
"indirect": { "indirect": {
"avh4/elm-color": "1.0.0",
"elm/bytes": "1.0.8", "elm/bytes": "1.0.8",
"elm/file": "1.0.5", "elm/file": "1.0.5",
"elm/parser": "1.1.0", "elm/parser": "1.1.0",

View File

@ -1,24 +0,0 @@
<!doctype HTML>
<html>
<head>
<title>twitch.tforgione.fr</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/ico" href="favicon.ico"/>
</head>
<body>
<div id="container"></div>
<script src="js/polymny-video-full.min.js"></script>
<script src="js/main.js"></script>
<script>
PolymnyVideo.fullpage({
node: document.getElementById('container'),
url: "videos/" + PolymnyVideo.getArgumentFromUrl("v") + "/manifest.m3u8",
autoplay: true,
startTime: PolymnyVideo.getArgumentFromUrl("t"),
enableMiniatures: true,
});
</script>
</body>
</html>

View File

@ -4,13 +4,91 @@
<title>twitch.tforgione.fr</title> <title>twitch.tforgione.fr</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/ico" href="favicon.ico"/> <link rel="icon" type="image/ico" href="/favicon.ico"/>
<link rel="stylesheet" href="https://cdn.plyr.io/3.6.7/plyr.css" />
<link href="css/spinner.css" rel="stylesheet">
<style>
video, .plyr, .plyr__video-wrapper {
height: 100%;
max-height: 100%;
}
</style>
</head> </head>
<body> <body>
<div id="container"></div> <div id="container"></div>
<script src="js/polymny-video-elm.min.js"></script> <script src="https://cdn.plyr.io/3.6.7/plyr.polyfilled.js"></script>
<script src="https://cdn.rawgit.com/video-dev/hls.js/18bb552/dist/hls.min.js"></script>
<script src="js/main.js"></script> <script src="js/main.js"></script>
<script> <script>
customElements.define('plyr-video',
class PlyrVideo extends HTMLElement {
constructor() {
super();
this.videoElement = document.createElement('video');
this.videoElement.setAttribute('controls', 'true');
}
static get observedAttributes() { return []; }
connectedCallback() {
this.appendChild(this.videoElement)
const hls = new Hls();
hls.loadSource(this.getAttribute('src'));
hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
// Transform available levels into an array of integers (height values).
const availableQualities = hls.levels.map((l) => l.height);
availableQualities.unshift(0);
// Initialize here
new Plyr(this.videoElement, {
quality: {
default: 0,
options: availableQualities,
// this ensures Plyr to use Hls to update quality level
forced: true,
onChange: updateQuality,
debug: true,
},
fullscreen: {
enabled: true,
fallback: true,
},
});
});
hls.on(Hls.Events.LEVEL_SWITCHED, function (event, data) {
var span = document.querySelector(".plyr__menu__container [data-plyr='quality'][value='0'] span");
if (hls.autoLevelEnabled) {
span.innerHTML = "Auto (" + hls.levels[data.level].height + "p)";
} else {
span.innerHTML = "Auto";
}
var x = document.querySelectorAll(".plyr__menu__container [data-plyr='settings'] span")[3];
if (x.innerHTML.startsWith("Auto") || x.innerHTML === "0p") {
x.innerHTML = span.innerHTML;
}
})
hls.attachMedia(this.videoElement);
function updateQuality(newQuality) {
if (newQuality === 0) {
hls.currentLevel = -1;
} else {
hls.levels.forEach((level, levelIndex) => {
if (level.height === newQuality) {
hls.currentLevel = levelIndex;
}
});
}
}
}
}
);
function isDarkMode(e) { function isDarkMode(e) {
var darkMode = JSON.parse(localStorage.getItem('darkMode')); var darkMode = JSON.parse(localStorage.getItem('darkMode'));
@ -35,7 +113,7 @@
} }
}); });
PolymnyVideo.setup(app); var lastId;
if (app.ports !== undefined) { if (app.ports !== undefined) {
if (app.ports.setDarkMode !== undefined) { if (app.ports.setDarkMode !== undefined) {
@ -50,19 +128,9 @@
} }
} }
if (app.ports !== undefined) {
if (app.ports.eraseVideo !== undefined) {
app.ports.eraseVideo.subscribe(function() {
window.scrollTo(0, 0);
lastId = undefined;
});
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
app.ports.darkMode.send(isDarkMode(e)); app.ports.darkMode.send(isDarkMode(e));
}); });
}
</script> </script>
</body> </body>
</html> </html>

562
js/vd.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -13,8 +13,6 @@ import Time
import TimeZone import TimeZone
import Twitch import Twitch
import Url import Url
import Video
import Video.Events
type alias Model = type alias Model =
@ -34,7 +32,7 @@ type alias Model =
type Page type Page
= Home (Maybe (Hover Twitch.Playlist)) = Home (Maybe (Hover Twitch.Playlist))
| Playlist Twitch.Playlist (Maybe (Hover Twitch.Video)) | Playlist Twitch.Playlist (Maybe (Hover Twitch.Video))
| Video Twitch.Playlist Twitch.Video Video.Video (Maybe (Hover Twitch.Video)) | Video Twitch.Playlist Twitch.Video (Maybe (Hover Twitch.Video))
type Msg type Msg
@ -55,7 +53,6 @@ type Msg
| CurrentDateReceived Time.Posix | CurrentDateReceived Time.Posix
| DarkMode Bool | DarkMode Bool
| DarkModeClicked | DarkModeClicked
| VideoMsg Video.Msg
init : { width : Int, height : Int, darkMode : Bool, darkSetting : Maybe Bool } -> Url.Url -> Nav.Key -> ( Model, Cmd Msg ) init : { width : Int, height : Int, darkMode : Bool, darkSetting : Maybe Bool } -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
@ -90,19 +87,11 @@ resultToMsg result =
subscriptions : Model -> Sub Msg subscriptions : Model -> Sub Msg
subscriptions model = subscriptions _ =
Sub.batch Sub.batch
[ Events.onResize (\w h -> SizeReceived w h) [ Events.onResize (\w h -> SizeReceived w h)
, Time.every 200 TimeReceived , Time.every 200 TimeReceived
, Ports.darkMode DarkMode , Ports.darkMode DarkMode
, Sub.map VideoMsg
(case model.page of
Video _ _ v _ ->
Video.Events.subs v
_ ->
Sub.none
)
] ]
@ -140,8 +129,8 @@ update msg model =
Playlist p Nothing -> Playlist p Nothing ->
( { model | page = Playlist p (Just (Hover.hover hover model.time)) }, Cmd.none ) ( { model | page = Playlist p (Just (Hover.hover hover model.time)) }, Cmd.none )
Video p v x Nothing -> Video p v Nothing ->
( { model | page = Video p v x (Just (Hover.hover hover model.time)) }, Cmd.none ) ( { model | page = Video p v (Just (Hover.hover v model.time)) }, Cmd.none )
_ -> _ ->
( model, Cmd.none ) ( model, Cmd.none )
@ -154,8 +143,8 @@ update msg model =
Playlist p _ -> Playlist p _ ->
( { model | page = Playlist p Nothing }, Cmd.none ) ( { model | page = Playlist p Nothing }, Cmd.none )
Video p v x _ -> Video p v _ ->
( { model | page = Video p v x Nothing }, Cmd.none ) ( { model | page = Video p v Nothing }, Cmd.none )
SizeReceived w h -> SizeReceived w h ->
( { model | device = Element.classifyDevice { width = w, height = h } } ( { model | device = Element.classifyDevice { width = w, height = h } }
@ -242,32 +231,18 @@ update msg model =
_ -> _ ->
Nothing Nothing
( page, cmd ) = page =
case ( playlist, video ) of case ( playlist, video ) of
( Just p, Just v ) -> ( Just p, Just v ) ->
let Video p v Nothing
( el, videoCommand ) =
Video.fromConfig
{ url = "/videos/" ++ p.url ++ v.url ++ "/manifest.m3u8"
, id = "video"
, autoplay = True
, enableMiniatures = True
, startTime = time
, customElement = Nothing
, live = Just False
, miniaturesUrl = Nothing
, muted = False
}
in
( Video p v el Nothing, Cmd.map VideoMsg videoCommand )
( Just p, Nothing ) -> ( Just p, Nothing ) ->
( Playlist p Nothing, Cmd.none ) Playlist p Nothing
_ -> _ ->
( Home Nothing, Cmd.none ) Home Nothing
in in
( { model | page = page }, cmd ) ( { model | page = page }, Cmd.none )
UrlRequested u -> UrlRequested u ->
case u of case u of
@ -280,22 +255,6 @@ update msg model =
DarkMode dark -> DarkMode dark ->
( { model | darkMode = dark }, Cmd.none ) ( { model | darkMode = dark }, Cmd.none )
VideoMsg vMsg ->
let
( newPage, newCommand ) =
case model.page of
Video a b v c ->
let
( newVideo, cmd ) =
Video.update vMsg v
in
( Video a b newVideo c, cmd )
_ ->
( model.page, Cmd.none )
in
( { model | page = newPage }, Cmd.map VideoMsg newCommand )
splitter : String -> Maybe ( String, String ) splitter : String -> Maybe ( String, String )
splitter input = splitter input =

View File

@ -1,4 +1,10 @@
port module Ports exposing (darkMode, setDarkMode) port module Ports exposing (darkMode, eraseVideo, registerVideo, setDarkMode)
port registerVideo : ( String, String, Maybe String ) -> Cmd msg
port eraseVideo : () -> Cmd msg
port setDarkMode : Maybe Bool -> Cmd msg port setDarkMode : Maybe Bool -> Cmd msg

View File

@ -12,13 +12,13 @@ import Element.Font as Font
import Element.Input as Input import Element.Input as Input
import Element.Keyed as Keyed import Element.Keyed as Keyed
import Hover exposing (Hover) import Hover exposing (Hover)
import Html
import Html.Attributes import Html.Attributes
import Json.Encode as Encode
import Time import Time
import TimeUtils import TimeUtils
import Twitch import Twitch
import Ui import Ui
import Video
import Video.Views
view : Core.Model -> Browser.Document Core.Msg view : Core.Model -> Browser.Document Core.Msg
@ -57,7 +57,7 @@ title model =
Core.Playlist p _ -> Core.Playlist p _ ->
Consts.url ++ " - " ++ p.name Consts.url ++ " - " ++ p.name
Core.Video p v _ _ -> Core.Video p v _ ->
Consts.url ++ " - " ++ p.name ++ " - " ++ v.name Consts.url ++ " - " ++ p.name ++ " - " ++ v.name
@ -70,8 +70,8 @@ viewContent model =
Core.Playlist playlist hover -> Core.Playlist playlist hover ->
videoMiniaturesView model.darkMode model.device model.zone model.currentDate model.time hover playlist videoMiniaturesView model.darkMode model.device model.zone model.currentDate model.time hover playlist
Core.Video playlist video v hover -> Core.Video playlist video hover ->
videoView model.darkMode model.device model.zone model.currentDate model.time hover playlist video v videoView model.darkMode model.device model.zone model.currentDate model.time hover playlist video
topBar : Maybe Bool -> Element Core.Msg topBar : Maybe Bool -> Element Core.Msg
@ -293,6 +293,8 @@ videoMiniature currentDate time hover playlist video =
, Element.height (Element.px 0) , Element.height (Element.px 0)
, Element.htmlAttribute (Html.Attributes.style "padding-top" "56.25%") , Element.htmlAttribute (Html.Attributes.style "padding-top" "56.25%")
, Element.htmlAttribute (Html.Attributes.style "position" "relative") , Element.htmlAttribute (Html.Attributes.style "position" "relative")
, Events.onMouseEnter (Core.HoverVideo video)
, Events.onMouseLeave Core.Unhover
] ]
( key ( key
, Element.image , Element.image
@ -316,14 +318,9 @@ videoMiniatureView : Bool -> Time.Zone -> Time.Posix -> Time.Posix -> Maybe (Hov
videoMiniatureView darkMode zone currentDate time hover playlist video = videoMiniatureView darkMode zone currentDate time hover playlist video =
let let
display = display =
Element.column Element.column [ Element.width Element.fill, Element.spacing 10 ]
[ Element.width Element.fill
, Element.spacing 10
, Events.onMouseEnter (Core.HoverVideo video)
, Events.onMouseLeave Core.Unhover
]
[ videoMiniature currentDate time hover playlist video [ videoMiniature currentDate time hover playlist video
, videoDate darkMode zone video , videoDescription darkMode zone video
] ]
button = button =
@ -339,16 +336,11 @@ videoInList : Bool -> Time.Zone -> Time.Posix -> Time.Posix -> Maybe (Hover Twit
videoInList darkMode zone currentDate time hover playlist activeVideo video = videoInList darkMode zone currentDate time hover playlist activeVideo video =
let let
label = label =
Element.row Element.row [ Element.width Element.fill, Element.spacing 10 ]
[ Element.width Element.fill
, Element.spacing 10
, Events.onMouseEnter (Core.HoverVideo video)
, Events.onMouseLeave Core.Unhover
]
[ Element.el [ Element.width (Element.fillPortion 2) ] [ Element.el [ Element.width (Element.fillPortion 2) ]
(videoMiniature currentDate time hover playlist video) (videoMiniature currentDate time hover playlist video)
, Element.el [ Element.width (Element.fillPortion 3), Element.paddingXY 0 10, Element.alignTop ] , Element.el [ Element.width (Element.fillPortion 3), Element.paddingXY 0 10, Element.alignTop ]
(videoDate darkMode zone video) (videoDescription darkMode zone video)
] ]
in in
if video == activeVideo then if video == activeVideo then
@ -367,8 +359,8 @@ videoInList darkMode zone currentDate time hover playlist activeVideo video =
} }
videoView : Bool -> Element.Device -> Time.Zone -> Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Video.Video -> Element Core.Msg videoView : Bool -> Element.Device -> Time.Zone -> Time.Posix -> Time.Posix -> Maybe (Hover Twitch.Video) -> Twitch.Playlist -> Twitch.Video -> Element Core.Msg
videoView darkMode device zone currentDate time hover playlist video v = videoView darkMode device zone currentDate time hover playlist video =
let let
( builder, contentPadding ) = ( builder, contentPadding ) =
case device.class of case device.class of
@ -387,8 +379,13 @@ videoView darkMode device zone currentDate time hover playlist video v =
[ Element.width (Element.fillPortion 2) [ Element.width (Element.fillPortion 2)
, Element.spacing 10 , Element.spacing 10
, Element.alignTop , Element.alignTop
, Element.height Element.fill
] ]
[ Video.Views.embedElement v |> Element.map Core.VideoMsg [ Element.html
(Html.node "plyr-video"
[ Html.Attributes.attribute "src" ("videos/" ++ playlist.url ++ video.url ++ "manifest.m3u8") ]
[]
)
, Element.paragraph , Element.paragraph
[ Font.size Consts.homeFontSize [ Font.size Consts.homeFontSize
, Font.bold , Font.bold
@ -416,8 +413,8 @@ videoView darkMode device zone currentDate time hover playlist video v =
] ]
videoDate : Bool -> Time.Zone -> Twitch.Video -> Element Core.Msg videoDescription : Bool -> Time.Zone -> Twitch.Video -> Element Core.Msg
videoDate darkMode zone video = videoDescription darkMode zone video =
Element.column [ Element.spacing 10 ] Element.column [ Element.spacing 10 ]
[ Element.paragraph [ Element.paragraph
[ Font.bold [ Font.bold
@ -562,7 +559,22 @@ groupAux size list acc =
spinner : Element Core.Msg spinner : Element Core.Msg
spinner = spinner =
Element.none Element.html
(Html.div [ Html.Attributes.class "lds-spinner" ]
[ Html.div [] []
, Html.div [] []
, Html.div [] []
, Html.div [] []
, Html.div [] []
, Html.div [] []
, Html.div [] []
, Html.div [] []
, Html.div [] []
, Html.div [] []
, Html.div [] []
, Html.div [] []
]
)
newBadge : Element Core.Msg newBadge : Element Core.Msg