Working on quality change

This commit is contained in:
Thomas Forgione 2021-06-11 11:39:12 +02:00
parent 8d10add379
commit e99a5ed1e3
4 changed files with 348 additions and 59 deletions

View File

@ -40,10 +40,12 @@
} }
}); });
let hls;
app.ports.initVideo.subscribe(function(arg) { app.ports.initVideo.subscribe(function(arg) {
const video = document.getElementById('video'); const video = document.getElementById('video');
if (Hls.isSupported()) { if (Hls.isSupported()) {
const hls = new Hls(); hls = new Hls();
hls.loadSource(arg); hls.loadSource(arg);
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) { hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
@ -54,16 +56,10 @@
}); });
hls.on(Hls.Events.LEVEL_SWITCHED, function (event, data) { hls.on(Hls.Events.LEVEL_SWITCHED, function (event, data) {
// var span = document.querySelector(".plyr__menu__container [data-plyr='quality'][value='0'] span"); app.ports.nowHasQuality.send({
// if (hls.autoLevelEnabled) { auto: hls.autoLevelEnabled,
// span.innerHTML = "Auto (" + hls.levels[data.level].height + "p)"; height: hls.levels[data.level].height
// } 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(video); hls.attachMedia(video);
@ -93,6 +89,30 @@
app.ports.exitFullscreen.subscribe(function() { app.ports.exitFullscreen.subscribe(function() {
document.exitFullscreen(); document.exitFullscreen();
}); });
app.ports.setPlaybackRate.subscribe(function(arg) {
const video = document.getElementById('video');
video.playbackRate = arg;
});
app.ports.setQuality.subscribe(function(arg) {
var old = hls.currentLevel;
if (arg.auto) {
hls.currentLevel = -1;
} else {
hls.levels.forEach((level, levelIndex) => {
if (level.height === arg.height) {
hls.currentLevel = levelIndex;
}
});
}
if (old === hls.currentLevel) {
app.ports.nowHasQuality.send({
auto: hls.autoLevelEnabled,
height: hls.currentLevel === -1 ? 0 : hls.levels[hls.currentLevel].height
});
}
});
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,10 +1,4 @@
module Icons exposing module Icons exposing (..)
( maximize
, minimize
, pause
, play
, playCircle
)
import Element exposing (Element) import Element exposing (Element)
import Html exposing (Html) import Html exposing (Html)
@ -69,3 +63,18 @@ playCircle =
[ Svg.circle [ cx "12", cy "12", r "10" ] [] [ Svg.circle [ cx "12", cy "12", r "10" ] []
, Svg.polygon [ points "10 8 16 12 10 16 10 8" ] [] , Svg.polygon [ points "10 8 16 12 10 16 10 8" ] []
] ]
settings : Bool -> Element msg
settings =
svgFeatherIcon "settings"
[ Svg.circle [ cx "12", cy "12", r "3" ] []
, Svg.path [ d "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" ] []
]
check : Bool -> Element msg
check =
svgFeatherIcon "check"
[ Svg.polyline [ points "20 6 9 17 4 12" ] []
]

View File

@ -3,7 +3,7 @@ port module Main exposing (..)
import Browser import Browser
import Browser.Events import Browser.Events
import DOM as Dom import DOM as Dom
import Element exposing (Element, alignRight, centerY, el, fill, padding, rgb255, row, spacing, text, width) import Element exposing (Element)
import Element.Background as Background import Element.Background as Background
import Element.Border as Border import Element.Border as Border
import Element.Font as Font import Element.Font as Font
@ -13,6 +13,7 @@ import Html.Attributes
import Html.Events import Html.Events
import Icons import Icons
import Json.Decode as Decode import Json.Decode as Decode
import Quality
import Simple.Animation as Animation exposing (Animation) import Simple.Animation as Animation exposing (Animation)
import Simple.Animation.Animated as Animated import Simple.Animation.Animated as Animated
import Simple.Animation.Property as P import Simple.Animation.Property as P
@ -28,6 +29,15 @@ main =
\_ -> \_ ->
Sub.batch Sub.batch
[ nowHasQualities NowHasQualities [ nowHasQualities NowHasQualities
, nowHasQuality
(\x ->
case Decode.decodeValue Quality.decode x of
Ok s ->
NowHasQuality s
_ ->
Noop
)
, Browser.Events.onAnimationFrameDelta AnimationFrameDelta , Browser.Events.onAnimationFrameDelta AnimationFrameDelta
, Browser.Events.onResize (\x y -> NowHasWindowSize ( x, y )) , Browser.Events.onResize (\x y -> NowHasWindowSize ( x, y ))
] ]
@ -45,18 +55,32 @@ type alias Model =
, volume : Float , volume : Float
, muted : Bool , muted : Bool
, isFullscreen : Bool , isFullscreen : Bool
, quality : Maybe Quality.Quality
, qualities : List Int , qualities : List Int
, showBar : Bool , showBar : Bool
, animationFrame : Float , animationFrame : Float
, videoSize : ( Int, Int ) , videoSize : ( Int, Int )
, screenSize : ( Int, Int ) , screenSize : ( Int, Int )
, playbackRate : Float
, settings : Settings
, showSettings : Bool
} }
type Settings
= All
| Speed
| Quality
type Msg type Msg
= Noop = Noop
| PlayPause | PlayPause
| Seek Float | Seek Float
| ToggleSettings
| SetSettings Settings
| SetPlaybackRate Float
| SetQuality Quality.Quality
| RequestFullscreen | RequestFullscreen
| ExitFullscreen | ExitFullscreen
| AnimationFrameDelta Float | AnimationFrameDelta Float
@ -69,8 +93,10 @@ type Msg
| NowLoaded (List ( Float, Float )) | NowLoaded (List ( Float, Float ))
| NowIsFullscreen Bool | NowIsFullscreen Bool
| NowHasQualities (List Int) | NowHasQualities (List Int)
| NowHasQuality Quality.Quality
| NowHasVideoSize ( Int, Int ) | NowHasVideoSize ( Int, Int )
| NowHasWindowSize ( Int, Int ) | NowHasWindowSize ( Int, Int )
| NowHasPlaybackRate Float
init : Decode.Value -> ( Model, Cmd Msg ) init : Decode.Value -> ( Model, Cmd Msg )
@ -97,11 +123,15 @@ init flags =
1.0 1.0
False False
False False
Nothing
[] []
True True
0 0
( 0, 0 ) ( 0, 0 )
( width, height ) ( width, height )
1.0
All
False
, initVideo url , initVideo url
) )
@ -118,13 +148,29 @@ update msg model =
Seek ratio -> Seek ratio ->
( model, seek (ratio * model.duration) ) ( model, seek (ratio * model.duration) )
SetPlaybackRate rate ->
( { model | showSettings = False, settings = All }, setPlaybackRate rate )
ToggleSettings ->
( { model | showSettings = not model.showSettings }, Cmd.none )
SetSettings s ->
( { model | settings = s }, Cmd.none )
RequestFullscreen -> RequestFullscreen ->
( model, requestFullscreen () ) ( model, requestFullscreen () )
ExitFullscreen -> ExitFullscreen ->
( model, exitFullscreen () ) ( model, exitFullscreen () )
SetQuality q ->
( { model | showSettings = False, settings = All }, setQuality q )
AnimationFrameDelta delta -> AnimationFrameDelta delta ->
if model.animationFrame + delta > 3500 then
( { model | animationFrame = model.animationFrame + delta, showSettings = False, settings = All }, Cmd.none )
else
( { model | animationFrame = model.animationFrame + delta }, Cmd.none ) ( { model | animationFrame = model.animationFrame + delta }, Cmd.none )
MouseMove -> MouseMove ->
@ -154,12 +200,18 @@ update msg model =
NowHasQualities qualities -> NowHasQualities qualities ->
( { model | qualities = qualities }, Cmd.none ) ( { model | qualities = qualities }, Cmd.none )
NowHasQuality quality ->
( { model | quality = Just quality }, Cmd.none )
NowHasVideoSize size -> NowHasVideoSize size ->
( { model | videoSize = size }, Cmd.none ) ( { model | videoSize = size }, Cmd.none )
NowHasWindowSize size -> NowHasWindowSize size ->
( { model | screenSize = size }, Cmd.none ) ( { model | screenSize = size }, Cmd.none )
NowHasPlaybackRate rate ->
( { model | playbackRate = rate }, Cmd.none )
view : Model -> Browser.Document Msg view : Model -> Browser.Document Msg
view model = view model =
@ -220,9 +272,13 @@ video model =
[ Element.width Element.fill, Element.height Element.fill ] [ Element.width Element.fill, Element.height Element.fill ]
(Element.column (Element.column
[ Element.width Element.fill [ Element.width Element.fill
, Element.padding 10
, Element.alignBottom , Element.alignBottom
, Font.color (Element.rgba 1 1 1 0.85) , Font.color (Element.rgba 1 1 1 0.85)
]
[ settings model
, Element.column
[ Element.width Element.fill
, Element.padding 10
, Background.gradient { angle = 0, steps = [ Element.rgba 0 0 0 0.75, Element.rgba 0 0 0 0 ] } , Background.gradient { angle = 0, steps = [ Element.rgba 0 0 0 0.75, Element.rgba 0 0 0 0 ] }
] ]
[ Element.row [ Element.row
@ -266,7 +322,8 @@ video model =
[ playPauseButton model.playing [ playPauseButton model.playing
, Element.el [ Element.moveDown 2.5 ] (Element.text (formatTime model.position ++ " / " ++ formatTime model.duration)) , Element.el [ Element.moveDown 2.5 ] (Element.text (formatTime model.position ++ " / " ++ formatTime model.duration))
, Element.row [ Element.spacing 10, Element.alignRight ] , Element.row [ Element.spacing 10, Element.alignRight ]
[ fullscreenButton model.isFullscreen ] [ settingsButton, fullscreenButton model.isFullscreen ]
]
] ]
] ]
) )
@ -318,6 +375,134 @@ video model =
) )
settings : Model -> Element Msg
settings model =
let
makeMenuButton : Settings -> Element Msg -> Element Msg -> Element Msg
makeMenuButton s key value =
Input.button [ Element.width Element.fill, Element.paddingXY 0 10 ]
{ label =
Element.row [ Element.width Element.fill, Element.spacing 20 ]
[ Element.el [ Font.bold, Element.alignLeft ] key
, Element.el [ Element.alignRight ] value
]
, onPress = Just (SetSettings s)
}
speedButton =
makeMenuButton Speed (Element.text "Speed") (Element.text ("x" ++ String.fromFloat model.playbackRate))
qualityButton =
case model.quality of
Just q ->
makeMenuButton Quality (Element.text "Quality") (Element.text (Quality.toString q))
_ ->
Element.none
returnButton =
Input.button
[ Element.width Element.fill
, Element.paddingXY 0 10
, Border.widthEach
{ bottom = 1
, top = 0
, left = 0
, right = 0
}
, Border.color (Element.rgba 0.5 0.5 0.5 0.75)
]
{ label = Element.text "Return"
, onPress = Just (SetSettings All)
}
speedOptions =
[ 0.5, 0.75, 1, 1.5, 2 ]
|> List.map
(\x ->
Input.button [ Element.width Element.fill, Element.paddingXY 0 10 ]
{ label =
Element.row [ Element.width Element.fill ]
[ if x == model.playbackRate then
Icons.check False
else
Element.el [ Font.color (Element.rgba 0 0 0 0) ] (Icons.check False)
, Element.el
[ Element.paddingEach
{ left = 10
, right = 0
, top = 0
, bottom = 0
}
]
(Element.text ("x" ++ String.fromFloat x))
]
, onPress = Just (SetPlaybackRate x)
}
)
|> (\x -> returnButton :: x)
qualityOptions =
model.qualities
|> List.map
(\x ->
Input.button [ Element.width Element.fill, Element.paddingXY 0 10 ]
{ label =
Element.row [ Element.width Element.fill ]
[ if Quality.isSameOption (Just { auto = False, height = x }) model.quality then
Icons.check False
else
Element.el [ Font.color (Element.rgba 0 0 0 0) ] (Icons.check False)
, Element.el
[ Element.paddingEach
{ left = 10
, right = 0
, top = 0
, bottom = 0
}
]
(Element.text (Quality.toString { auto = False, height = x }))
]
, onPress = Just (SetQuality { auto = x == 0, height = x })
}
)
|> (\x -> returnButton :: x)
buttons =
case model.settings of
All ->
[ speedButton, qualityButton ]
Speed ->
speedOptions
Quality ->
qualityOptions
in
animatedEl
(if model.showSettings then
fadeIn
else
fadeOut
)
[ Element.padding 10
, Element.width Element.fill
, Element.height Element.fill
, Element.moveDown 20
]
(Element.column
[ Background.color (Element.rgba 0.2 0.2 0.2 0.75)
, Element.alignRight
, Element.paddingXY 20 10
, Border.rounded 10
]
buttons
)
playPauseButton : Bool -> Element Msg playPauseButton : Bool -> Element Msg
playPauseButton playing = playPauseButton playing =
let let
@ -349,6 +534,14 @@ fullscreenButton isFullscreen =
) )
settingsButton : Element Msg
settingsButton =
Input.button []
{ label = Icons.settings False
, onPress = Just ToggleSettings
}
playerEvents : List (Element.Attribute Msg) playerEvents : List (Element.Attribute Msg)
playerEvents = playerEvents =
List.map Element.htmlAttribute List.map Element.htmlAttribute
@ -367,6 +560,7 @@ videoEvents =
, Html.Events.on "volumechange" decodeVolumeChange , Html.Events.on "volumechange" decodeVolumeChange
, Html.Events.on "progress" decodeProgress , Html.Events.on "progress" decodeProgress
, Html.Events.on "resize" decodeVideoResize , Html.Events.on "resize" decodeVideoResize
, Html.Events.on "ratechange" decodePlaybackRateChange
] ]
@ -422,7 +616,7 @@ decodeTimeRanges =
decodeTimeRange : Decode.Decoder ( Float, Float ) decodeTimeRange : Decode.Decoder ( Float, Float )
decodeTimeRange = decodeTimeRange =
Decode.map2 (\x y -> ( x, y )) Decode.map2 Tuple.pair
(Decode.field "start" Decode.float) (Decode.field "start" Decode.float)
(Decode.field "end" Decode.float) (Decode.field "end" Decode.float)
@ -445,6 +639,13 @@ decodeVideoResize =
(Decode.field "videoHeight" Decode.int) (Decode.field "videoHeight" Decode.int)
decodePlaybackRateChange : Decode.Decoder Msg
decodePlaybackRateChange =
Dom.target <|
Decode.map NowHasPlaybackRate
(Decode.field "playbackRate" Decode.float)
every : Float -> List ( Float, Float ) -> List ( Float, Float, Bool ) every : Float -> List ( Float, Float ) -> List ( Float, Float, Bool )
every duration input = every duration input =
everyAux duration 0.0 [] input |> List.reverse |> List.filter (\( x, y, _ ) -> x /= y) everyAux duration 0.0 [] input |> List.reverse |> List.filter (\( x, y, _ ) -> x /= y)
@ -517,9 +718,18 @@ port requestFullscreen : () -> Cmd msg
port exitFullscreen : () -> Cmd msg port exitFullscreen : () -> Cmd msg
port setPlaybackRate : Float -> Cmd msg
port setQuality : Quality.Quality -> Cmd msg
port nowHasQualities : (List Int -> msg) -> Sub msg port nowHasQualities : (List Int -> msg) -> Sub msg
port nowHasQuality : (Decode.Value -> msg) -> Sub msg
fadeIn : Animation fadeIn : Animation
fadeIn = fadeIn =
Animation.fromTo Animation.fromTo

50
src/Quality.elm Normal file
View File

@ -0,0 +1,50 @@
module Quality exposing (Quality, decode, isSameOption, toString)
import Json.Decode as Decode
type alias Quality =
{ auto : Bool
, height : Int
}
toString : Quality -> String
toString { auto, height } =
if height == 0 then
"Auto"
else if auto then
"Auto (" ++ String.fromInt height ++ "p)"
else
String.fromInt height ++ "p"
isSameOption : Maybe Quality -> Maybe Quality -> Bool
isSameOption quality1 quality2 =
case ( quality1, quality2 ) of
( Just q1, Just q2 ) ->
autoHeight q1 == autoHeight q2
( Nothing, Nothing ) ->
True
_ ->
False
autoHeight : Quality -> Int
autoHeight { auto, height } =
if auto then
0
else
height
decode : Decode.Decoder Quality
decode =
Decode.map2 Quality
(Decode.field "auto" Decode.bool)
(Decode.field "height" Decode.int)