Adds support for captions, more shortcuts
This commit is contained in:
		
							parent
							
								
									3193f3c382
								
							
						
					
					
						commit
						80ef34d43b
					
				
							
								
								
									
										108
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								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 = []; |  | ||||||
|                     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", |                 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user