diff --git a/dashboard-ui/css/mediaplayer-video.css b/dashboard-ui/css/mediaplayer-video.css index 80fdb48c7b..c98d08f20c 100644 --- a/dashboard-ui/css/mediaplayer-video.css +++ b/dashboard-ui/css/mediaplayer-video.css @@ -358,4 +358,23 @@ video::-webkit-media-text-track-background { #videoPlayer:not(.idlePlayer) video:not([controls])::-webkit-media-text-track-display { /*Style the text itself*/ margin-top: -2.5em; -} \ No newline at end of file +} + +.videoSubtitles { + position: fixed; + z-index: 99997; + bottom: 10%; + text-align: center; + font-size: 400%; + left: 0; + right: 0; + color: #fff; +} + +.videoSubtitlesInner { + max-width: 70%; + background-color: rgba(0,0,0,.8); + padding: .25em; + margin: auto; + display: inline-block; +} diff --git a/dashboard-ui/css/site.css b/dashboard-ui/css/site.css index db45b5a82b..4ab0d0f5a2 100644 --- a/dashboard-ui/css/site.css +++ b/dashboard-ui/css/site.css @@ -1189,6 +1189,13 @@ paper-input + .fieldDescription { margin-bottom: 30px !important; } +.ui-content { + border-width: 0; + overflow: visible; + overflow-x: hidden; + padding: 1em; +} + .page > .ui-content, .pageWithAbsoluteTabs .pageTabContent { /* Need this so that the audio player doesn't cover content, but also for unveil lazy loading. */ padding-bottom: 160px; diff --git a/dashboard-ui/devices/ios/ios.css b/dashboard-ui/devices/ios/ios.css index e8a46c33c5..3c0ef2289b 100644 --- a/dashboard-ui/devices/ios/ios.css +++ b/dashboard-ui/devices/ios/ios.css @@ -242,3 +242,7 @@ div.cardBox { .localSyncStatus .labelSyncStatus { display: none !important; } + +.videoSubtitles { + font-size: 300%; +} diff --git a/dashboard-ui/scripts/htmlmediarenderer.js b/dashboard-ui/scripts/htmlmediarenderer.js index 237dc0123a..d39f944da1 100644 --- a/dashboard-ui/scripts/htmlmediarenderer.js +++ b/dashboard-ui/scripts/htmlmediarenderer.js @@ -12,6 +12,7 @@ var self = this; function onEnded() { + destroyCustomTrack(); Events.trigger(self, 'ended'); } @@ -31,6 +32,7 @@ // } // } //} + updateSubtitleText(this.currentTime * 1000); Events.trigger(self, 'timeupdate'); } @@ -68,6 +70,8 @@ function onError(e) { + destroyCustomTrack(); + var elem = e.target; var errorCode = elem.error ? elem.error.code : ''; console.log('Media element error code: ' + errorCode); @@ -271,6 +275,9 @@ }; self.stop = function () { + + destroyCustomTrack(); + if (mediaElement) { mediaElement.pause(); @@ -421,6 +428,7 @@ }; function setTracks(elem, tracks) { + var html = tracks.map(function (t) { var defaultAttribute = t.isDefault ? ' default' : ''; @@ -430,9 +438,7 @@ }).join(''); - if (html) { - elem.innerHTML = html; - } + elem.innerHTML = html; } self.currentSrc = function () { @@ -503,20 +509,127 @@ return supportsTextTracks; }; + function enableNativeTrackSupport(track) { + + if (browserInfo.safari) { + return false; + } + + return true; + } + + function destroyCustomTrack() { + + var videoSubtitlesElem = document.querySelector('.videoSubtitles'); + if (videoSubtitlesElem) { + videoSubtitlesElem.parentNode.removeChild(videoSubtitlesElem); + } + + currentSubtitlesElement = null; + currentTrackEvents = null; + customTrackIndex = -1; + } + + function fetchSubtitles(track) { + + return ApiClient.ajax({ + url: track.url.replace('.vtt', '.js'), + type: 'GET', + dataType: 'json' + }); + } + + function setTrackForCustomDisplay(track) { + + if (!track) { + destroyCustomTrack(); + return; + } + + // if already playing this track, skip + if (customTrackIndex == track.index) { + return; + } + + destroyCustomTrack(); + customTrackIndex = track.index; + + // download the track json + fetchSubtitles(track).then(function (data) { + + // show in ui + console.log('downloaded ' + data.TrackEvents.length + ' track events'); + currentTrackEvents = data.TrackEvents; + }); + } + + var currentSubtitlesElement; + var currentTrackEvents; + var customTrackIndex = -1; + var lastCustomTrackMs = 0; + function updateSubtitleText(timeMs) { + + var trackEvents = currentTrackEvents; + if (!trackEvents) { + return; + } + + if (!currentSubtitlesElement) { + var videoSubtitlesElem = document.querySelector('.videoSubtitles'); + if (!videoSubtitlesElem) { + videoSubtitlesElem = document.createElement('div'); + videoSubtitlesElem.classList.add('videoSubtitles'); + videoSubtitlesElem.innerHTML = '
'; + document.body.appendChild(videoSubtitlesElem); + } + currentSubtitlesElement = videoSubtitlesElem.querySelector('.videoSubtitlesInner'); + } + + if (lastCustomTrackMs > 0) { + if (Math.abs(lastCustomTrackMs - timeMs) < 500) { + return; + } + } + + var positionTicks = timeMs * 10000; + for (var i = 0, length = trackEvents.length; i < length; i++) { + + var caption = trackEvents[i]; + if (positionTicks >= caption.StartPositionTicks && positionTicks <= caption.EndPositionTicks) { + currentSubtitlesElement.innerHTML = caption.Text; + currentSubtitlesElement.classList.remove('hide'); + return; + } + } + + currentSubtitlesElement.innerHTML = ''; + currentSubtitlesElement.classList.add('hide'); + } + self.setCurrentTrackElement = function (streamIndex) { console.log('Setting new text track index to: ' + streamIndex); - var allTracks = mediaElement.textTracks; // get list of tracks - - var modes = ['disabled', 'showing', 'hidden']; - var expectedId = 'textTrack' + streamIndex; - var track = streamIndex == -1 ? null : currentTrackList.filter(function (t) { return t.index == streamIndex; })[0]; - var trackIndex = streamIndex == -1 || !track ? -1 : currentTrackList.indexOf(track); + if (enableNativeTrackSupport(track)) { + + setTrackForCustomDisplay(null); + } else { + setTrackForCustomDisplay(track); + + // null these out to disable the player's native display (handled below) + streamIndex = -1; + track = null; + } + + var expectedId = 'textTrack' + streamIndex; + var trackIndex = streamIndex == -1 || !track ? -1 : currentTrackList.indexOf(track); + var modes = ['disabled', 'showing', 'hidden']; + + var allTracks = mediaElement.textTracks; // get list of tracks for (var i = 0; i < allTracks.length; i++) { var currentTrack = allTracks[i]; diff --git a/dashboard-ui/thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.theme.css b/dashboard-ui/thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.theme.css index f31a12215a..9138cbe6db 100644 --- a/dashboard-ui/thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.theme.css +++ b/dashboard-ui/thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.theme.css @@ -261,13 +261,6 @@ div.ui-mobile-viewport { display: inline-block; } -.ui-content { - border-width: 0; - overflow: visible; - overflow-x: hidden; - padding: 1em; -} - /* Buttons and icons */ .ui-btn, ul[data-role="listview"] a + a { font-size: 16px; diff --git a/dashboard-ui/thirdparty/paper-button-style.css b/dashboard-ui/thirdparty/paper-button-style.css index 63ce30701e..6117cd5a10 100644 --- a/dashboard-ui/thirdparty/paper-button-style.css +++ b/dashboard-ui/thirdparty/paper-button-style.css @@ -7,6 +7,7 @@ paper-button.block { display: block; + margin: .25em 0; } paper-button.blue {