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 @@
+
\ 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 @@
-
-