Adds support for captions, more shortcuts
This commit is contained in:
parent
3193f3c382
commit
80ef34d43b
110
index.html
110
index.html
|
@ -14,112 +14,14 @@
|
||||||
<div id="container"></div>
|
<div id="container"></div>
|
||||||
<script src="https://cdn.rawgit.com/video-dev/hls.js/18bb552/dist/hls.min.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 src="js/ports.js"></script>
|
||||||
<script>
|
<script>
|
||||||
Object.defineProperty(TimeRanges.prototype, "asArray", {
|
embed({
|
||||||
get: function() {
|
node: document.getElementById("container"),
|
||||||
var ret = [];
|
url: "video/manifest.m3u8",
|
||||||
for (var i = 0; i < this.length; i++) {
|
|
||||||
ret.push({start: this.start(i), end: this.end(i)});
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(HTMLElement.prototype, "document", {
|
|
||||||
get: function() {
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const app = Elm.Main.init({
|
|
||||||
node: document.getElementById('container'),
|
|
||||||
flags: {
|
|
||||||
url: "video/manifest.m3u8",
|
|
||||||
width: window.innerWidth,
|
|
||||||
height: window.innerHeight
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let hls;
|
|
||||||
|
|
||||||
app.ports.polymnyVideoInit.subscribe(function(arg) {
|
|
||||||
const video = document.getElementById('video');
|
|
||||||
if (Hls.isSupported()) {
|
|
||||||
hls = new Hls();
|
|
||||||
hls.loadSource(arg);
|
|
||||||
|
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
|
|
||||||
// Transform available levels into an array of integers (height values).
|
|
||||||
const availableQualities = hls.levels.map((l) => l.height);
|
|
||||||
availableQualities.unshift(0);
|
|
||||||
app.ports.polymnyVideoNowHasQualities.send(availableQualities);
|
|
||||||
});
|
|
||||||
|
|
||||||
hls.on(Hls.Events.LEVEL_SWITCHED, function (event, data) {
|
|
||||||
app.ports.polymnyVideoNowHasQuality.send({
|
|
||||||
auto: hls.autoLevelEnabled,
|
|
||||||
height: hls.levels[data.level].height
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
hls.attachMedia(video);
|
|
||||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
|
||||||
video.src = arg;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.ports.polymnyVideoPlayPause.subscribe(function() {
|
|
||||||
const video = document.getElementById('video');
|
|
||||||
if (video.paused) {
|
|
||||||
video.play();
|
|
||||||
} else {
|
|
||||||
video.pause();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.ports.polymnyVideoSeek.subscribe(function(arg) {
|
|
||||||
const video = document.getElementById('video');
|
|
||||||
console.log(arg);
|
|
||||||
video.currentTime = arg;
|
|
||||||
});
|
|
||||||
|
|
||||||
app.ports.polymnyVideoRequestFullscreen.subscribe(function() {
|
|
||||||
document.getElementById('full').requestFullscreen();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.ports.polymnyVideoExitFullscreen.subscribe(function() {
|
|
||||||
document.exitFullscreen();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.ports.polymnyVideoSetPlaybackRate.subscribe(function(arg) {
|
|
||||||
const video = document.getElementById('video');
|
|
||||||
video.playbackRate = arg;
|
|
||||||
});
|
|
||||||
|
|
||||||
app.ports.polymnyVideoSetQuality.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.polymnyVideoNowHasQuality.send({
|
|
||||||
auto: hls.autoLevelEnabled,
|
|
||||||
height: hls.currentLevel === -1 ? 0 : hls.levels[hls.currentLevel].height
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.ports.polymnyVideoSetVolume.subscribe(function(arg) {
|
|
||||||
const video = document.getElementById('video');
|
|
||||||
video.volume = arg.volume;
|
|
||||||
video.muted = arg.muted;
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -21,6 +21,24 @@ subs model =
|
||||||
Ok s ->
|
Ok s ->
|
||||||
Video.NowHasQuality s
|
Video.NowHasQuality s
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Video.Noop
|
||||||
|
)
|
||||||
|
, Video.nowHasSubtitles
|
||||||
|
(\x ->
|
||||||
|
case Decode.decodeValue decodeSubtitles x of
|
||||||
|
Ok s ->
|
||||||
|
Video.NowHasSubtitles s
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Video.Noop
|
||||||
|
)
|
||||||
|
, Video.nowHasSubtitleTrack
|
||||||
|
(\x ->
|
||||||
|
case Decode.decodeValue decodeMaybeSubtitleTrack x of
|
||||||
|
Ok t ->
|
||||||
|
Video.NowHasSubtitleTrack t
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Video.Noop
|
Video.Noop
|
||||||
)
|
)
|
||||||
|
@ -185,6 +203,67 @@ decodeKeyDown model =
|
||||||
Video.RequestFullscreen
|
Video.RequestFullscreen
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- 0 key
|
||||||
|
48 ->
|
||||||
|
Decode.succeed (Video.Seek 0)
|
||||||
|
|
||||||
|
-- 1 key
|
||||||
|
49 ->
|
||||||
|
Decode.succeed (Video.Seek (0.1 * model.duration))
|
||||||
|
|
||||||
|
-- 2 key
|
||||||
|
50 ->
|
||||||
|
Decode.succeed (Video.Seek (0.2 * model.duration))
|
||||||
|
|
||||||
|
-- 3 key
|
||||||
|
51 ->
|
||||||
|
Decode.succeed (Video.Seek (0.3 * model.duration))
|
||||||
|
|
||||||
|
-- 4 key
|
||||||
|
52 ->
|
||||||
|
Decode.succeed (Video.Seek (0.4 * model.duration))
|
||||||
|
|
||||||
|
-- 5 key
|
||||||
|
53 ->
|
||||||
|
Decode.succeed (Video.Seek (0.5 * model.duration))
|
||||||
|
|
||||||
|
-- 6 key
|
||||||
|
54 ->
|
||||||
|
Decode.succeed (Video.Seek (0.6 * model.duration))
|
||||||
|
|
||||||
|
-- 7 key
|
||||||
|
55 ->
|
||||||
|
Decode.succeed (Video.Seek (0.7 * model.duration))
|
||||||
|
|
||||||
|
-- 8 key
|
||||||
|
56 ->
|
||||||
|
Decode.succeed (Video.Seek (0.8 * model.duration))
|
||||||
|
|
||||||
|
-- 9 key
|
||||||
|
57 ->
|
||||||
|
Decode.succeed (Video.Seek (0.9 * model.duration))
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Decode.fail ("no shortcut for code " ++ String.fromInt x)
|
Decode.fail ("no shortcut for code " ++ String.fromInt x)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
decodeSubtitleTrack : Decode.Decoder Video.SubtitleTrack
|
||||||
|
decodeSubtitleTrack =
|
||||||
|
Decode.map6 Video.SubtitleTrack
|
||||||
|
(Decode.field "name" Decode.string)
|
||||||
|
(Decode.field "groupId" Decode.string)
|
||||||
|
(Decode.field "type" Decode.string)
|
||||||
|
(Decode.field "autoselect" Decode.bool)
|
||||||
|
(Decode.field "default" Decode.bool)
|
||||||
|
(Decode.field "forced" Decode.bool)
|
||||||
|
|
||||||
|
|
||||||
|
decodeMaybeSubtitleTrack : Decode.Decoder (Maybe Video.SubtitleTrack)
|
||||||
|
decodeMaybeSubtitleTrack =
|
||||||
|
Decode.nullable decodeSubtitleTrack
|
||||||
|
|
||||||
|
|
||||||
|
decodeSubtitles : Decode.Decoder (List Video.SubtitleTrack)
|
||||||
|
decodeSubtitles =
|
||||||
|
Decode.list decodeSubtitleTrack
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
port module Video exposing (Msg(..), Settings(..), Video, fromUrl, init, nowHasQualities, nowHasQuality, update)
|
port module Video exposing
|
||||||
|
( Msg(..)
|
||||||
|
, Settings(..)
|
||||||
|
, SubtitleTrack
|
||||||
|
, Video
|
||||||
|
, fromUrl
|
||||||
|
, init
|
||||||
|
, nowHasQualities
|
||||||
|
, nowHasQuality
|
||||||
|
, nowHasSubtitleTrack
|
||||||
|
, nowHasSubtitles
|
||||||
|
, update
|
||||||
|
)
|
||||||
|
|
||||||
import Json.Decode as Decode
|
import Json.Decode as Decode
|
||||||
import Quality exposing (Quality)
|
import Quality exposing (Quality)
|
||||||
|
@ -21,6 +33,8 @@ type alias Video =
|
||||||
, playbackRate : Float
|
, playbackRate : Float
|
||||||
, settings : Settings
|
, settings : Settings
|
||||||
, showSettings : Bool
|
, showSettings : Bool
|
||||||
|
, subtitles : List SubtitleTrack
|
||||||
|
, subtitleTrack : Maybe SubtitleTrack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,6 +56,8 @@ fromUrl url =
|
||||||
, playbackRate = 1
|
, playbackRate = 1
|
||||||
, settings = All
|
, settings = All
|
||||||
, showSettings = False
|
, showSettings = False
|
||||||
|
, subtitles = []
|
||||||
|
, subtitleTrack = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,6 +65,17 @@ type Settings
|
||||||
= All
|
= All
|
||||||
| Speed
|
| Speed
|
||||||
| Quality
|
| Quality
|
||||||
|
| Subtitles
|
||||||
|
|
||||||
|
|
||||||
|
type alias SubtitleTrack =
|
||||||
|
{ name : String
|
||||||
|
, groupdId : String
|
||||||
|
, ty : String
|
||||||
|
, autoselect : Bool
|
||||||
|
, default : Bool
|
||||||
|
, forced : Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
|
@ -59,6 +86,7 @@ type Msg
|
||||||
| SetSettings Settings
|
| SetSettings Settings
|
||||||
| SetPlaybackRate Float
|
| SetPlaybackRate Float
|
||||||
| SetQuality Quality.Quality
|
| SetQuality Quality.Quality
|
||||||
|
| SetSubtitleTrack Int
|
||||||
| SetVolume Float Bool
|
| SetVolume Float Bool
|
||||||
| RequestFullscreen
|
| RequestFullscreen
|
||||||
| ExitFullscreen
|
| ExitFullscreen
|
||||||
|
@ -75,6 +103,8 @@ type Msg
|
||||||
| NowHasQuality Quality.Quality
|
| NowHasQuality Quality.Quality
|
||||||
| NowHasSize ( Int, Int )
|
| NowHasSize ( Int, Int )
|
||||||
| NowHasPlaybackRate Float
|
| NowHasPlaybackRate Float
|
||||||
|
| NowHasSubtitles (List SubtitleTrack)
|
||||||
|
| NowHasSubtitleTrack (Maybe SubtitleTrack)
|
||||||
|
|
||||||
|
|
||||||
update : Msg -> Video -> ( Video, Cmd Msg )
|
update : Msg -> Video -> ( Video, Cmd Msg )
|
||||||
|
@ -107,6 +137,9 @@ update msg model =
|
||||||
SetQuality q ->
|
SetQuality q ->
|
||||||
( { model | showSettings = False, settings = All }, setQuality q )
|
( { model | showSettings = False, settings = All }, setQuality q )
|
||||||
|
|
||||||
|
SetSubtitleTrack t ->
|
||||||
|
( { model | showSettings = False, settings = All }, setSubtitleTrack t )
|
||||||
|
|
||||||
SetVolume v m ->
|
SetVolume v m ->
|
||||||
( model, setVolume { volume = v, muted = m } )
|
( model, setVolume { volume = v, muted = m } )
|
||||||
|
|
||||||
|
@ -153,6 +186,12 @@ update msg model =
|
||||||
NowHasPlaybackRate rate ->
|
NowHasPlaybackRate rate ->
|
||||||
( { model | playbackRate = rate }, Cmd.none )
|
( { model | playbackRate = rate }, Cmd.none )
|
||||||
|
|
||||||
|
NowHasSubtitles tracks ->
|
||||||
|
( { model | subtitles = tracks }, Cmd.none )
|
||||||
|
|
||||||
|
NowHasSubtitleTrack track ->
|
||||||
|
( { model | subtitleTrack = track }, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
port polymnyVideoInit : String -> Cmd msg
|
port polymnyVideoInit : String -> Cmd msg
|
||||||
|
|
||||||
|
@ -210,6 +249,14 @@ setQuality =
|
||||||
polymnyVideoSetQuality
|
polymnyVideoSetQuality
|
||||||
|
|
||||||
|
|
||||||
|
port polymnyVideoSetSubtitleTrack : Int -> Cmd msg
|
||||||
|
|
||||||
|
|
||||||
|
setSubtitleTrack : Int -> Cmd msg
|
||||||
|
setSubtitleTrack =
|
||||||
|
polymnyVideoSetSubtitleTrack
|
||||||
|
|
||||||
|
|
||||||
port polymnyVideoSetVolume : { volume : Float, muted : Bool } -> Cmd msg
|
port polymnyVideoSetVolume : { volume : Float, muted : Bool } -> Cmd msg
|
||||||
|
|
||||||
|
|
||||||
|
@ -232,3 +279,19 @@ port polymnyVideoNowHasQuality : (Decode.Value -> msg) -> Sub msg
|
||||||
nowHasQuality : (Decode.Value -> msg) -> Sub msg
|
nowHasQuality : (Decode.Value -> msg) -> Sub msg
|
||||||
nowHasQuality =
|
nowHasQuality =
|
||||||
polymnyVideoNowHasQuality
|
polymnyVideoNowHasQuality
|
||||||
|
|
||||||
|
|
||||||
|
port polymnyVideoNowHasSubtitles : (Decode.Value -> msg) -> Sub msg
|
||||||
|
|
||||||
|
|
||||||
|
nowHasSubtitles : (Decode.Value -> msg) -> Sub msg
|
||||||
|
nowHasSubtitles =
|
||||||
|
polymnyVideoNowHasSubtitles
|
||||||
|
|
||||||
|
|
||||||
|
port polymnyVideoNowHasSubtitleTrack : (Decode.Value -> msg) -> Sub msg
|
||||||
|
|
||||||
|
|
||||||
|
nowHasSubtitleTrack : (Decode.Value -> msg) -> Sub msg
|
||||||
|
nowHasSubtitleTrack =
|
||||||
|
polymnyVideoNowHasSubtitleTrack
|
||||||
|
|
|
@ -65,7 +65,7 @@ embed screenSize model =
|
||||||
else
|
else
|
||||||
fadeOut
|
fadeOut
|
||||||
)
|
)
|
||||||
[ Element.width Element.fill, Element.height Element.fill ]
|
[ Element.width Element.fill, Element.alignBottom ]
|
||||||
(Element.column
|
(Element.column
|
||||||
[ Element.width Element.fill
|
[ Element.width Element.fill
|
||||||
, Element.alignBottom
|
, Element.alignBottom
|
||||||
|
@ -197,6 +197,17 @@ settings model =
|
||||||
_ ->
|
_ ->
|
||||||
Element.none
|
Element.none
|
||||||
|
|
||||||
|
subtitlesButton =
|
||||||
|
case ( model.subtitleTrack, model.subtitles ) of
|
||||||
|
( Just t, _ :: _ ) ->
|
||||||
|
makeMenuButton Video.Subtitles (Element.text "Subtitles") (Element.text t.name)
|
||||||
|
|
||||||
|
( _, _ :: _ ) ->
|
||||||
|
makeMenuButton Video.Subtitles (Element.text "Subtitles") (Element.text "Disabled")
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Element.none
|
||||||
|
|
||||||
returnButton =
|
returnButton =
|
||||||
Input.button
|
Input.button
|
||||||
[ Element.width Element.fill
|
[ Element.width Element.fill
|
||||||
|
@ -267,16 +278,48 @@ settings model =
|
||||||
)
|
)
|
||||||
|> (\x -> returnButton :: x)
|
|> (\x -> returnButton :: x)
|
||||||
|
|
||||||
|
subtitleOptions =
|
||||||
|
model.subtitles
|
||||||
|
|> List.indexedMap (\i x -> ( i, Just x ))
|
||||||
|
|> (\x -> ( -1, Nothing ) :: x)
|
||||||
|
|> List.map
|
||||||
|
(\( i, x ) ->
|
||||||
|
Input.button [ Element.width Element.fill, Element.paddingXY 0 10 ]
|
||||||
|
{ label =
|
||||||
|
Element.row [ Element.width Element.fill ]
|
||||||
|
[ if Maybe.map .name model.subtitleTrack == Maybe.map .name x 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 (Maybe.withDefault "Disabled" (Maybe.map .name x)))
|
||||||
|
]
|
||||||
|
, onPress = Just (Video.SetSubtitleTrack i)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> (\x -> returnButton :: x)
|
||||||
|
|
||||||
buttons =
|
buttons =
|
||||||
case model.settings of
|
case model.settings of
|
||||||
Video.All ->
|
Video.All ->
|
||||||
[ speedButton, qualityButton ]
|
[ speedButton, qualityButton, subtitlesButton ]
|
||||||
|
|
||||||
Video.Speed ->
|
Video.Speed ->
|
||||||
speedOptions
|
speedOptions
|
||||||
|
|
||||||
Video.Quality ->
|
Video.Quality ->
|
||||||
qualityOptions
|
qualityOptions
|
||||||
|
|
||||||
|
Video.Subtitles ->
|
||||||
|
subtitleOptions
|
||||||
in
|
in
|
||||||
animatedEl
|
animatedEl
|
||||||
(if model.showSettings then
|
(if model.showSettings then
|
||||||
|
|
Loading…
Reference in New Issue