diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 1588be5d22..79037e7733 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1637,7 +1637,22 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla self.supportSubtitleOffset = function(player) { player = player || self._currentPlayer; - return 'setSubtitleOffset' in player; + return player && 'setSubtitleOffset' in player; + } + + self.enableShowingSubtitleOffset = function(player) { + player = player || self._currentPlayer; + player.enableShowingSubtitleOffset(); + } + + self.disableShowingSubtitleOffset = function(player) { + player = player || self._currentPlayer; + player.disableShowingSubtitleOffset(); + } + + self.isShowingSubtitleOffsetEnabled = function(player) { + player = player || self._currentPlayer; + return player.isShowingSubtitleOffsetEnabled(); } self.isSubtitleStreamExternal = function(index, player) { @@ -1645,17 +1660,20 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla return stream ? getDeliveryMethod(stream) === 'External' : false; } - self.setSubtitleOffset = function (sliderValue, player) { + self.setSubtitleOffset = function (value, player) { player = player || self._currentPlayer; - player.setSubtitleOffset(self.getOffsetFromSliderValue(sliderValue)); + player.setSubtitleOffset(value); }; - self.getOffsetFromSliderValue = function(value) { - var offset = (value - 50) / 50; - // multiply by offset min/max range value (-x to +x) : - offset *= 30; - return offset.toFixed(1); - }; + self.getPlayerSubtitleOffset = function(player) { + player = player || self._currentPlayer; + return player.getSubtitleOffset(); + } + + self.canHandleOffsetOnCurrentSubtitle = function(player) { + var index = self.getSubtitleStreamIndex(player); + return index !== -1 && self.isSubtitleStreamExternal(index, player); + } self.seek = function (ticks, player) { diff --git a/src/components/playback/playersettingsmenu.js b/src/components/playback/playersettingsmenu.js index b51027fe0f..369245211f 100644 --- a/src/components/playback/playersettingsmenu.js +++ b/src/components/playback/playersettingsmenu.js @@ -231,6 +231,15 @@ define(['connectionManager', 'actionsheet', 'datetime', 'playbackManager', 'glob }); } + if (options.suboffset) { + + menuItems.push({ + name: globalize.translate('SubtitleOffset'), + id: 'suboffset', + asideText: null + }); + } + menuItems.push({ name: globalize.translate('SubtitleSettings'), id: 'subtitlesettings' @@ -302,6 +311,11 @@ define(['connectionManager', 'actionsheet', 'datetime', 'playbackManager', 'glob options.onOption('stats'); } return Promise.resolve(); + case 'suboffset': + if (options.onOption) { + options.onOption('suboffset'); + } + return Promise.resolve(); default: break; } diff --git a/src/components/subtitlesync/subtitlesync.css b/src/components/subtitlesync/subtitlesync.css new file mode 100644 index 0000000000..112e624722 --- /dev/null +++ b/src/components/subtitlesync/subtitlesync.css @@ -0,0 +1,48 @@ +.subtitleSyncContainer { + width: 40%; + margin-left: 30%; + margin-right: 30%; + height: 4.2em; + background: rgba(28,28,28,0.8); + border-radius: .3em; + color: #fff; + position: absolute; +} + +.subtitleSync-closeButton { + position: absolute; + top: 0; + right: 0; + color: #ccc; + z-index: 2; +} + +.subtitleSyncTextField { + position: absolute; + left: 0; + width: 40%; + margin-left: 30%; + margin-right: 30%; + top: 0.1em; + text-align: center; + font-size: 20px; + color: white; + z-index: 2; +} + +#prompt { + flex-shrink: 0; +} + +.subtitleSyncSliderContainer { + width: 98%; + margin-left: 1%; + margin-right: 1%; + top: 2.5em; + height: 1.4em; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + border-radius: .3em; + z-index: 1; +} \ No newline at end of file diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js new file mode 100644 index 0000000000..3389b7bf89 --- /dev/null +++ b/src/components/subtitlesync/subtitlesync.js @@ -0,0 +1,163 @@ +define(['playbackManager', 'text!./subtitlesync.template.html', 'css!./subtitlesync'], function (playbackManager, template, css) { + "use strict"; + + var player; + var subtitleSyncSlider; + var subtitleSyncTextField; + var subtitleSyncCloseButton; + var subtitleSyncContainer; + + function init(instance) { + + var parent = document.createElement('div'); + parent.innerHTML = template; + + subtitleSyncSlider = parent.querySelector(".subtitleSyncSlider"); + subtitleSyncTextField = parent.querySelector(".subtitleSyncTextField"); + subtitleSyncCloseButton = parent.querySelector(".subtitleSync-closeButton"); + subtitleSyncContainer = parent.querySelector(".subtitleSyncContainer"); + + subtitleSyncContainer.classList.add("hide"); + + subtitleSyncTextField.updateOffset = function(offset) { + this.textContent = offset + "s"; + } + + subtitleSyncTextField.addEventListener("keypress", function(event) { + + if(event.key === "Enter"){ + // if input key is enter search for float pattern + var inputOffset = /[-+]?\d+\.?\d*/g.exec(this.textContent); + if(inputOffset) { + inputOffset = inputOffset[0]; + + // replace current text by considered offset + this.textContent = inputOffset + "s"; + + inputOffset = parseFloat(inputOffset); + // set new offset + playbackManager.setSubtitleOffset(inputOffset, player); + // synchronize with slider value + subtitleSyncSlider.updateOffset( + getPercentageFromOffset(inputOffset)); + } else { + this.textContent = (playbackManager.getPlayerSubtitleOffset(player) || 0) + "s"; + } + this.hasFocus = false; + event.preventDefault(); + } else { + // keep focus to prevent fade with bottom layout + this.hasFocus = true; + if(event.key.match(/[+-\d.s]/) === null) { + event.preventDefault(); + } + } + }); + + subtitleSyncSlider.updateOffset = function(percent) { + // default value is 0s = 50% + this.value = percent === undefined ? 50 : percent; + } + + subtitleSyncSlider.addEventListener("change", function () { + // set new offset + playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player); + // synchronize with textField value + subtitleSyncTextField.updateOffset( + getOffsetFromPercentage(this.value)); + }); + + subtitleSyncSlider.addEventListener("touchmove", function () { + // set new offset + playbackManager.setSubtitleOffset(getOffsetFromPercentage(this.value), player); + // synchronize with textField value + subtitleSyncTextField.updateOffset( + getOffsetFromPercentage(this.value)); + }); + + subtitleSyncSlider.getBubbleHtml = function (value) { + var newOffset = getOffsetFromPercentage(value); + return '

' + + (newOffset > 0 ? "+" : "") + parseFloat(newOffset) + "s" + + "

"; + }; + + subtitleSyncCloseButton.addEventListener("click", function() { + playbackManager.disableShowingSubtitleOffset(player); + SubtitleSync.prototype.toggle("forceToHide"); + }); + + document.body.appendChild(parent); + + instance.element = parent; + } + + + function getOffsetFromPercentage(value) { + // convert percent to fraction + var offset = (value - 50) / 50; + // multiply by offset min/max range value (-x to +x) : + offset *= 30; + return offset.toFixed(1); + }; + + function getPercentageFromOffset(value) { + // divide by offset min/max range value (-x to +x) : + var percentValue = value / 30; + // convert fraction to percent + percentValue *= 50; + percentValue += 50; + return Math.min(100, Math.max(0, percentValue.toFixed())); + }; + + function SubtitleSync(currentPlayer) { + player = currentPlayer; + init(this); + } + + SubtitleSync.prototype.destroy = function(){ + SubtitleSync.prototype.toggle("forceToHide"); + if(player){ + playbackManager.disableShowingSubtitleOffset(player); + playbackManager.setSubtitleOffset(0, player); + } + var elem = this.element; + if (elem) { + elem.parentNode.removeChild(elem); + this.element = null; + } + } + + SubtitleSync.prototype.toggle = function(action) { + + if(player && playbackManager.supportSubtitleOffset(player)){ + + switch(action) { + case undefined: + // if showing subtitle sync is enabled + if(playbackManager.isShowingSubtitleOffsetEnabled(player) && + // if there is an external subtitle stream enabled + playbackManager.canHandleOffsetOnCurrentSubtitle(player)){ + // if no subtitle offset is defined + if(!playbackManager.getPlayerSubtitleOffset(player)) { + // set default offset to '0' = 50% + subtitleSyncSlider.value = "50"; + subtitleSyncTextField.textContent = "0s"; + playbackManager.setSubtitleOffset(0, player); + } + // show subtitle sync + subtitleSyncContainer.classList.remove("hide"); + break; // stop here + } // else continue and hide + case "hide": + if(subtitleSyncTextField.hasFocus){break;} // else continue and hide + case "forceToHide": + subtitleSyncContainer.classList.add("hide"); + break; + } + + } + } + + return SubtitleSync; +}); diff --git a/src/components/subtitlesync/subtitlesync.template.html b/src/components/subtitlesync/subtitlesync.template.html new file mode 100644 index 0000000000..4ee344333d --- /dev/null +++ b/src/components/subtitlesync/subtitlesync.template.html @@ -0,0 +1,7 @@ +
+ +
0s
+
+ +
+
\ No newline at end of file diff --git a/src/controllers/videoosd.js b/src/controllers/videoosd.js index 00ce862f23..68c0024f7d 100644 --- a/src/controllers/videoosd.js +++ b/src/controllers/videoosd.js @@ -280,7 +280,6 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med btnFastForward.disabled = true; btnRewind.disabled = true; view.querySelector(".btnSubtitles").classList.add("hide"); - view.querySelector(".subtitleSyncSliderContainer").classList.add("hide"); view.querySelector(".btnAudio").classList.add("hide"); view.querySelector(".osdTitle").innerHTML = ""; view.querySelector(".osdMediaInfo").innerHTML = ""; @@ -296,23 +295,10 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med if (playbackManager.subtitleTracks(player).length) { view.querySelector(".btnSubtitles").classList.remove("hide"); - - if(playbackManager.supportSubtitleOffset()) { - var index = playbackManager.getSubtitleStreamIndex(player); - // if there is an external subtitle stream enabled - if(index !== -1 && playbackManager.isSubtitleStreamExternal(index, player)){ - // show subtitle sync slider - subtitleSyncSliderContainer.classList.remove("hide"); - }else{ - // hide subtitle sync slider - subtitleSyncSliderContainer.classList.add("hide"); - } - } - + toggleSubtitleSync(); } else { view.querySelector(".btnSubtitles").classList.add("hide"); - // hide subtitle sync slider - subtitleSyncSliderContainer.classList.add("hide"); + toggleSubtitleSync("forceToHide"); } if (playbackManager.audioTracks(player).length > 1) { @@ -435,6 +421,7 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med focusManager.focus(elem.querySelector(".btnPause")); }, 50); } + toggleSubtitleSync(); } } @@ -447,6 +434,7 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med once: true }); currentVisibleMenu = null; + toggleSubtitleSync("hide"); } } @@ -638,6 +626,7 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med function releaseCurrentPlayer() { destroyStats(); + destroySubtitleSync(); resetUpNextDialog(); var player = currentPlayer; @@ -913,11 +902,17 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med var player = currentPlayer; if (player) { + + // show subtitle offset feature only if player and media support it + var showSubOffset = playbackManager.supportSubtitleOffset(player) && + playbackManager.canHandleOffsetOnCurrentSubtitle(player); + playerSettingsMenu.show({ mediaType: "Video", player: player, positionTo: btn, stats: true, + suboffset: showSubOffset, onOption: onSettingsOption }); } @@ -927,6 +922,12 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med function onSettingsOption(selectedOption) { if ("stats" === selectedOption) { toggleStats(); + } else if ("suboffset" === selectedOption) { + var player = currentPlayer; + if (player) { + playbackManager.enableShowingSubtitleOffset(player); + toggleSubtitleSync(); + } } } @@ -1024,32 +1025,30 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med if (index !== currentIndex) { playbackManager.setSubtitleStreamIndex(index, player); } - return id; - }).then(function (id) { - var index = parseInt(id); - // on subtitle stream change - if (playbackManager.supportSubtitleOffset() && index !== currentIndex) { - - /// if there is an external subtitle stream enabled - if (index !== -1 && playbackManager.isSubtitleStreamExternal(index, player)){ - - // set default offset to '0' (slider's middle value) - var subtitleSyncSliderMiddleValue = 50; - subtitleSyncSlider.value = subtitleSyncSliderMiddleValue.toString(); - playbackManager.setSubtitleOffset(subtitleSyncSliderMiddleValue, player); - - // show subtitle sync slider - subtitleSyncSliderContainer.classList.remove("hide"); - } else { - // hide subtitle sync slider - subtitleSyncSliderContainer.classList.add("hide"); - } - } + toggleSubtitleSync(); }); }); } + function toggleSubtitleSync(action) { + require(["subtitleSync"], function (SubtitleSync) { + var player = currentPlayer; + if (subtitleSyncOverlay) { + subtitleSyncOverlay.toggle(action); + } else if(player){ + subtitleSyncOverlay = new SubtitleSync(player); + } + }); + } + + function destroySubtitleSync() { + if (subtitleSyncOverlay) { + subtitleSyncOverlay.destroy(); + subtitleSyncOverlay = null; + } + } + function onWindowKeyDown(e) { if (!currentVisibleMenu && (32 === e.keyCode || 13 === e.keyCode)) { playbackManager.playPause(currentPlayer); @@ -1184,8 +1183,7 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med var programStartDateMs = 0; var programEndDateMs = 0; var playbackStartTimeTicks = 0; - var subtitleSyncSlider = view.querySelector(".subtitleSyncSlider"); - var subtitleSyncSliderContainer = view.querySelector(".subtitleSyncSliderContainer"); + var subtitleSyncOverlay; var nowPlayingVolumeSlider = view.querySelector(".osdVolumeSlider"); var nowPlayingVolumeSliderContainer = view.querySelector(".osdVolumeSliderContainer"); var nowPlayingPositionSlider = view.querySelector(".osdPositionSlider"); @@ -1257,6 +1255,7 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med } destroyStats(); + destroySubtitleSync(); }); var lastPointerDown = 0; dom.addEventListener(view, window.PointerEvent ? "pointerdown" : "click", function (e) { @@ -1309,20 +1308,6 @@ define(["playbackManager", "dom", "inputmanager", "datetime", "itemHelper", "med playbackManager.setVolume(this.value, currentPlayer); }); - subtitleSyncSlider.addEventListener("change", function () { - playbackManager.setSubtitleOffset(this.value, currentPlayer); - }); - subtitleSyncSlider.addEventListener("touchmove", function () { - playbackManager.setSubtitleOffset(this.value, currentPlayer); - }); - - subtitleSyncSlider.getBubbleHtml = function (value) { - var newOffset = playbackManager.getOffsetFromSliderValue(value); - return '

' + - (newOffset > 0 ? "+" : "") + parseFloat(newOffset) + "s" + - "

"; - }; - nowPlayingPositionSlider.addEventListener("change", function () { var player = currentPlayer; diff --git a/src/css/videoosd.css b/src/css/videoosd.css index 5151f87d63..bd24e41309 100644 --- a/src/css/videoosd.css +++ b/src/css/videoosd.css @@ -144,13 +144,6 @@ flex-grow: 1 } -.subtitleSyncSliderContainer { - width: 12em; - -webkit-box-flex: 1; - -webkit-flex-grow: 1; - flex-grow: 1 -} - .osdMediaInfo, .volumeButtons { display: -webkit-box; diff --git a/src/scripts/site.js b/src/scripts/site.js index f984f20ebb..6471343ed8 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -758,6 +758,7 @@ var AppInfo = {}; define("recordingButton", [componentsPath + "/recordingcreator/recordingbutton"], returnFirstDependency); define("recordingHelper", [componentsPath + "/recordingcreator/recordinghelper"], returnFirstDependency); define("subtitleEditor", [componentsPath + "/subtitleeditor/subtitleeditor"], returnFirstDependency); + define("subtitleSync", [componentsPath + "/subtitlesync/subtitlesync"], returnFirstDependency); define("itemIdentifier", [componentsPath + "/itemidentifier/itemidentifier"], returnFirstDependency); define("mediaInfo", [componentsPath + "/mediainfo/mediainfo"], returnFirstDependency); define("itemContextMenu", [componentsPath + "/itemcontextmenu"], returnFirstDependency); diff --git a/src/videoosd.html b/src/videoosd.html index d5c117a4d4..72b93aa83b 100644 --- a/src/videoosd.html +++ b/src/videoosd.html @@ -1,4 +1,4 @@ -
+
@@ -60,12 +60,6 @@ -
-
- -
-
-