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> | ||||
|         <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/ports.js"></script> | ||||
|         <script> | ||||
|             Object.defineProperty(TimeRanges.prototype, "asArray", { | ||||
|                 get: function() { | ||||
|                     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: { | ||||
|             embed({ | ||||
|                 node: document.getElementById("container"), | ||||
|                 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> | ||||
|     </body> | ||||
| </html> | ||||
|  | ||||
| @ -21,6 +21,24 @@ subs model = | ||||
|                     Ok 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 | ||||
|             ) | ||||
| @ -185,6 +203,67 @@ decodeKeyDown model = | ||||
|                                 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) | ||||
|             ) | ||||
| 
 | ||||
| 
 | ||||
| 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 Quality exposing (Quality) | ||||
| @ -21,6 +33,8 @@ type alias Video = | ||||
|     , playbackRate : Float | ||||
|     , settings : Settings | ||||
|     , showSettings : Bool | ||||
|     , subtitles : List SubtitleTrack | ||||
|     , subtitleTrack : Maybe SubtitleTrack | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| @ -42,6 +56,8 @@ fromUrl url = | ||||
|     , playbackRate = 1 | ||||
|     , settings = All | ||||
|     , showSettings = False | ||||
|     , subtitles = [] | ||||
|     , subtitleTrack = Nothing | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| @ -49,6 +65,17 @@ type Settings | ||||
|     = All | ||||
|     | Speed | ||||
|     | Quality | ||||
|     | Subtitles | ||||
| 
 | ||||
| 
 | ||||
| type alias SubtitleTrack = | ||||
|     { name : String | ||||
|     , groupdId : String | ||||
|     , ty : String | ||||
|     , autoselect : Bool | ||||
|     , default : Bool | ||||
|     , forced : Bool | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| type Msg | ||||
| @ -59,6 +86,7 @@ type Msg | ||||
|     | SetSettings Settings | ||||
|     | SetPlaybackRate Float | ||||
|     | SetQuality Quality.Quality | ||||
|     | SetSubtitleTrack Int | ||||
|     | SetVolume Float Bool | ||||
|     | RequestFullscreen | ||||
|     | ExitFullscreen | ||||
| @ -75,6 +103,8 @@ type Msg | ||||
|     | NowHasQuality Quality.Quality | ||||
|     | NowHasSize ( Int, Int ) | ||||
|     | NowHasPlaybackRate Float | ||||
|     | NowHasSubtitles (List SubtitleTrack) | ||||
|     | NowHasSubtitleTrack (Maybe SubtitleTrack) | ||||
| 
 | ||||
| 
 | ||||
| update : Msg -> Video -> ( Video, Cmd Msg ) | ||||
| @ -107,6 +137,9 @@ update msg model = | ||||
|         SetQuality q -> | ||||
|             ( { model | showSettings = False, settings = All }, setQuality q ) | ||||
| 
 | ||||
|         SetSubtitleTrack t -> | ||||
|             ( { model | showSettings = False, settings = All }, setSubtitleTrack t ) | ||||
| 
 | ||||
|         SetVolume v m -> | ||||
|             ( model, setVolume { volume = v, muted = m } ) | ||||
| 
 | ||||
| @ -153,6 +186,12 @@ update msg model = | ||||
|         NowHasPlaybackRate rate -> | ||||
|             ( { model | playbackRate = rate }, Cmd.none ) | ||||
| 
 | ||||
|         NowHasSubtitles tracks -> | ||||
|             ( { model | subtitles = tracks }, Cmd.none ) | ||||
| 
 | ||||
|         NowHasSubtitleTrack track -> | ||||
|             ( { model | subtitleTrack = track }, Cmd.none ) | ||||
| 
 | ||||
| 
 | ||||
| port polymnyVideoInit : String -> Cmd msg | ||||
| 
 | ||||
| @ -210,6 +249,14 @@ setQuality = | ||||
|     polymnyVideoSetQuality | ||||
| 
 | ||||
| 
 | ||||
| port polymnyVideoSetSubtitleTrack : Int -> Cmd msg | ||||
| 
 | ||||
| 
 | ||||
| setSubtitleTrack : Int -> Cmd msg | ||||
| setSubtitleTrack = | ||||
|     polymnyVideoSetSubtitleTrack | ||||
| 
 | ||||
| 
 | ||||
| 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 = | ||||
|     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 | ||||
|                     fadeOut | ||||
|                 ) | ||||
|                 [ Element.width Element.fill, Element.height Element.fill ] | ||||
|                 [ Element.width Element.fill, Element.alignBottom ] | ||||
|                 (Element.column | ||||
|                     [ Element.width Element.fill | ||||
|                     , Element.alignBottom | ||||
| @ -197,6 +197,17 @@ settings model = | ||||
|                 _ -> | ||||
|                     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 = | ||||
|             Input.button | ||||
|                 [ Element.width Element.fill | ||||
| @ -267,16 +278,48 @@ settings model = | ||||
|                     ) | ||||
|                 |> (\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 = | ||||
|             case model.settings of | ||||
|                 Video.All -> | ||||
|                     [ speedButton, qualityButton ] | ||||
|                     [ speedButton, qualityButton, subtitlesButton ] | ||||
| 
 | ||||
|                 Video.Speed -> | ||||
|                     speedOptions | ||||
| 
 | ||||
|                 Video.Quality -> | ||||
|                     qualityOptions | ||||
| 
 | ||||
|                 Video.Subtitles -> | ||||
|                     subtitleOptions | ||||
|     in | ||||
|     animatedEl | ||||
|         (if model.showSettings then | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user