mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
initial timeshifting support
This commit is contained in:
parent
2971ec0e69
commit
92e4d82bd2
12 changed files with 78 additions and 62 deletions
|
@ -5,6 +5,7 @@
|
||||||
border: none;
|
border: none;
|
||||||
max-height: 84%;
|
max-height: 84%;
|
||||||
border-radius: 1px !important;
|
border-radius: 1px !important;
|
||||||
|
box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionsheet-fullscreen {
|
.actionsheet-fullscreen {
|
||||||
|
|
|
@ -46,7 +46,7 @@ define(['browser'], function (browser) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function canPlayHlsWithMSE() {
|
function canPlayHlsWithMSE() {
|
||||||
if (window.MediaSource != null && !browser.firefox) {
|
if (window.MediaSource != null) {
|
||||||
// text tracks don’t work with this in firefox
|
// text tracks don’t work with this in firefox
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -389,7 +389,7 @@ define(['browser'], function (browser) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can't use mkv on mobile because we have to use the native player controls and they won't be able to seek it
|
// Can't use mkv on mobile because we have to use the native player controls and they won't be able to seek it
|
||||||
if (canPlayMkv && options.supportsCustomSeeking && !browser.tizen) {
|
if (canPlayMkv && options.supportsCustomSeeking && !browser.tizen && options.enableMkvProgressive !== false) {
|
||||||
profile.TranscodingProfiles.push({
|
profile.TranscodingProfiles.push({
|
||||||
Container: 'mkv',
|
Container: 'mkv',
|
||||||
Type: 'Video',
|
Type: 'Video',
|
||||||
|
@ -400,7 +400,7 @@ define(['browser'], function (browser) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canPlayTs && options.supportsCustomSeeking && !browser.tizen && !browser.web0s) {
|
if (canPlayTs && options.supportsCustomSeeking && !browser.tizen && !browser.web0s && options.enableTsProgressive !== false) {
|
||||||
profile.TranscodingProfiles.push({
|
profile.TranscodingProfiles.push({
|
||||||
Container: 'ts',
|
Container: 'ts',
|
||||||
Type: 'Video',
|
Type: 'Video',
|
||||||
|
@ -414,7 +414,7 @@ define(['browser'], function (browser) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canPlayHls()) {
|
if (canPlayHls() && options.enableHls !== false) {
|
||||||
profile.TranscodingProfiles.push({
|
profile.TranscodingProfiles.push({
|
||||||
Container: 'ts',
|
Container: 'ts',
|
||||||
Type: 'Video',
|
Type: 'Video',
|
||||||
|
|
|
@ -30,15 +30,18 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're
|
||||||
formDialogContent.style['max-height'] = '60%';
|
formDialogContent.style['max-height'] = '60%';
|
||||||
scrollHelper.centerFocus.on(formDialogContent, false);
|
scrollHelper.centerFocus.on(formDialogContent, false);
|
||||||
} else {
|
} else {
|
||||||
var minWidth = (Math.min(options.buttons.length * 150, dom.getWindowSize().innerWidth - 50));
|
dlg.style.maxWidth = (Math.min((options.buttons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)) + 'px';
|
||||||
dlg.style.maxWidth = (minWidth + 200) + 'px';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
|
//dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
|
||||||
// dialogHelper.close(dlg);
|
// dialogHelper.close(dlg);
|
||||||
//});
|
//});
|
||||||
|
|
||||||
|
if (options.title) {
|
||||||
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title || '';
|
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title || '';
|
||||||
|
} else {
|
||||||
|
dlg.querySelector('.formDialogHeaderTitle').classList.add('hide');
|
||||||
|
}
|
||||||
|
|
||||||
dlg.querySelector('.text').innerHTML = options.html || options.text || '';
|
dlg.querySelector('.text').innerHTML = options.html || options.text || '';
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,12 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.4);
|
|
||||||
border: 0;
|
border: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
/* Strict does not work well with actionsheet */
|
/* Strict does not work well with actionsheet */
|
||||||
contain: style;
|
contain: style;
|
||||||
|
box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), 0 6px 30px 5px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-fixedSize {
|
.dialog-fixedSize {
|
||||||
|
@ -37,6 +37,7 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: 1280px), all and (max-height: 720px) {
|
@media all and (max-width: 1280px), all and (max-height: 720px) {
|
||||||
|
@ -47,6 +48,7 @@
|
||||||
left: 0 !important;
|
left: 0 !important;
|
||||||
right: 0 !important;
|
right: 0 !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||||
<i class="md-icon">check</i><span>${ButtonSave}</span>
|
<span>${ButtonSave}</span>
|
||||||
</button>
|
</button>
|
||||||
<button is="emby-button" type="button" id="btnResetEasyPassword" class="raised cancel block hide">
|
<button is="emby-button" type="button" id="btnResetEasyPassword" class="raised cancel block hide">
|
||||||
<i class="md-icon">lock</i>
|
<i class="md-icon">lock</i>
|
||||||
|
|
|
@ -328,8 +328,6 @@
|
||||||
|
|
||||||
$('.sliderValue', elem).html(tooltext);
|
$('.sliderValue', elem).html(tooltext);
|
||||||
|
|
||||||
console.log("slidin", pct, self.currentDurationTicks, time);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,7 +253,10 @@
|
||||||
self.duration = function (val) {
|
self.duration = function (val) {
|
||||||
|
|
||||||
if (mediaElement) {
|
if (mediaElement) {
|
||||||
return mediaElement.duration;
|
var duration = mediaElement.duration;
|
||||||
|
if (duration && !isNaN(duration) && duration != Number.POSITIVE_INFINITY && duration != Number.NEGATIVE_INFINITY) {
|
||||||
|
return duration * 1000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -386,19 +386,15 @@
|
||||||
name: Globalize.translate('ButtonNo'),
|
name: Globalize.translate('ButtonNo'),
|
||||||
id: 'no'
|
id: 'no'
|
||||||
});
|
});
|
||||||
menuItems.push({
|
|
||||||
name: Globalize.translate('ButtonCancel'),
|
|
||||||
id: 'cancel'
|
|
||||||
});
|
|
||||||
|
|
||||||
require(['actionsheet'], function (actionsheet) {
|
require(['dialog'], function (dialog) {
|
||||||
|
|
||||||
actionsheet.show({
|
dialog({
|
||||||
items: menuItems,
|
buttons: menuItems,
|
||||||
//positionTo: positionTo,
|
//positionTo: positionTo,
|
||||||
title: Globalize.translate('ConfirmEndPlayerSession'),
|
text: Globalize.translate('ConfirmEndPlayerSession')
|
||||||
callback: function (id) {
|
|
||||||
|
|
||||||
|
}).then(function (id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
|
|
||||||
case 'yes':
|
case 'yes':
|
||||||
|
@ -411,7 +407,6 @@
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -595,24 +595,11 @@
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSeekableDuration() {
|
|
||||||
|
|
||||||
if (self.currentMediaSource && self.currentMediaSource.RunTimeTicks) {
|
|
||||||
return self.currentMediaSource.RunTimeTicks;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.currentMediaRenderer) {
|
|
||||||
return self.getCurrentTicks(self.currentMediaRenderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPositionSliderChange() {
|
function onPositionSliderChange() {
|
||||||
|
|
||||||
var newPercent = parseFloat(this.value);
|
var newPercent = parseFloat(this.value);
|
||||||
|
|
||||||
var newPositionTicks = (newPercent / 100) * getSeekableDuration();
|
var newPositionTicks = (newPercent / 100) * self.getSeekableDurationTicks();
|
||||||
|
|
||||||
self.changeStream(Math.floor(newPositionTicks));
|
self.changeStream(Math.floor(newPositionTicks));
|
||||||
}
|
}
|
||||||
|
@ -808,7 +795,7 @@
|
||||||
|
|
||||||
positionSlider.getBubbleText = function (value) {
|
positionSlider.getBubbleText = function (value) {
|
||||||
|
|
||||||
var seekableDuration = getSeekableDuration();
|
var seekableDuration = self.getSeekableDurationTicks();
|
||||||
if (!self.currentMediaSource || !seekableDuration) {
|
if (!self.currentMediaSource || !seekableDuration) {
|
||||||
return '--:--';
|
return '--:--';
|
||||||
}
|
}
|
||||||
|
@ -1016,7 +1003,7 @@
|
||||||
// Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts
|
// Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts
|
||||||
// This will start the transcoding process before actually feeding the video url into the player
|
// This will start the transcoding process before actually feeding the video url into the player
|
||||||
// Edit: Also seeing stalls from hls.js
|
// Edit: Also seeing stalls from hls.js
|
||||||
if (!mediaSource.RunTimeTicks && isHls) {
|
if (!mediaSource.RunTimeTicks && isHls && !browserInfo.edge) {
|
||||||
|
|
||||||
Dashboard.showLoadingMsg();
|
Dashboard.showLoadingMsg();
|
||||||
var hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8');
|
var hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8');
|
||||||
|
@ -1170,7 +1157,6 @@
|
||||||
document.body.classList.add('bodyWithPopupOpen');
|
document.body.classList.add('bodyWithPopupOpen');
|
||||||
|
|
||||||
self.currentMediaRenderer = mediaRenderer;
|
self.currentMediaRenderer = mediaRenderer;
|
||||||
self.currentDurationTicks = self.currentMediaSource.RunTimeTicks;
|
|
||||||
|
|
||||||
self.updateNowPlayingInfo(item);
|
self.updateNowPlayingInfo(item);
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
||||||
self.currentItem = null;
|
self.currentItem = null;
|
||||||
self.currentMediaSource = null;
|
self.currentMediaSource = null;
|
||||||
|
|
||||||
self.currentDurationTicks = null;
|
|
||||||
self.startTimeTicksOffset = null;
|
self.startTimeTicksOffset = null;
|
||||||
|
|
||||||
self.playlist = [];
|
self.playlist = [];
|
||||||
|
@ -146,7 +145,7 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
||||||
|
|
||||||
// viblast can help us here
|
// viblast can help us here
|
||||||
//return true;
|
//return true;
|
||||||
return window.MediaSource && !browserInfo.firefox;
|
return window.MediaSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.changeStream = function (ticks, params) {
|
self.changeStream = function (ticks, params) {
|
||||||
|
@ -166,7 +165,13 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
||||||
var playSessionId = getParameterByName('PlaySessionId', currentSrc);
|
var playSessionId = getParameterByName('PlaySessionId', currentSrc);
|
||||||
var liveStreamId = getParameterByName('LiveStreamId', currentSrc);
|
var liveStreamId = getParameterByName('LiveStreamId', currentSrc);
|
||||||
|
|
||||||
Dashboard.getDeviceProfile().then(function (deviceProfile) {
|
Dashboard.getDeviceProfile(null, {
|
||||||
|
|
||||||
|
enableMkvProgressive: self.currentMediaSource.RunTimeTicks != null,
|
||||||
|
enableTsProgressive: self.currentMediaSource.RunTimeTicks != null,
|
||||||
|
enableHls: !browserInfo.firefox || self.currentMediaSource.RunTimeTicks == null
|
||||||
|
|
||||||
|
}).then(function (deviceProfile) {
|
||||||
|
|
||||||
var audioStreamIndex = params.AudioStreamIndex == null ? (getParameterByName('AudioStreamIndex', currentSrc) || null) : params.AudioStreamIndex;
|
var audioStreamIndex = params.AudioStreamIndex == null ? (getParameterByName('AudioStreamIndex', currentSrc) || null) : params.AudioStreamIndex;
|
||||||
if (typeof (audioStreamIndex) == 'string') {
|
if (typeof (audioStreamIndex) == 'string') {
|
||||||
|
@ -264,6 +269,22 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
||||||
//self.updateTextStreamUrls(streamInfo.startTimeTicksOffset || 0);
|
//self.updateTextStreamUrls(streamInfo.startTimeTicksOffset || 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.getSeekableDurationTicks = function () {
|
||||||
|
|
||||||
|
if (self.currentMediaSource && self.currentMediaSource.RunTimeTicks) {
|
||||||
|
return self.currentMediaSource.RunTimeTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.currentMediaRenderer) {
|
||||||
|
var duration = self.currentMediaRenderer.duration();
|
||||||
|
if (duration) {
|
||||||
|
return duration * 10000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
self.setCurrentTime = function (ticks, positionSlider, currentTimeElement) {
|
self.setCurrentTime = function (ticks, positionSlider, currentTimeElement) {
|
||||||
|
|
||||||
// Convert to ticks
|
// Convert to ticks
|
||||||
|
@ -272,13 +293,15 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
||||||
var timeText = datetime.getDisplayRunningTime(ticks);
|
var timeText = datetime.getDisplayRunningTime(ticks);
|
||||||
var mediaRenderer = self.currentMediaRenderer;
|
var mediaRenderer = self.currentMediaRenderer;
|
||||||
|
|
||||||
if (self.currentDurationTicks) {
|
var seekableDurationTicks = self.getSeekableDurationTicks();
|
||||||
|
|
||||||
timeText += " / " + datetime.getDisplayRunningTime(self.currentDurationTicks);
|
if (seekableDurationTicks) {
|
||||||
|
|
||||||
|
timeText += " / " + datetime.getDisplayRunningTime(seekableDurationTicks);
|
||||||
|
|
||||||
if (positionSlider) {
|
if (positionSlider) {
|
||||||
|
|
||||||
var percent = ticks / self.currentDurationTicks;
|
var percent = ticks / seekableDurationTicks;
|
||||||
percent *= 100;
|
percent *= 100;
|
||||||
|
|
||||||
positionSlider.value = percent;
|
positionSlider.value = percent;
|
||||||
|
@ -287,7 +310,7 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
||||||
|
|
||||||
if (positionSlider) {
|
if (positionSlider) {
|
||||||
|
|
||||||
positionSlider.disabled = !((self.currentDurationTicks || 0) > 0 || canPlayerSeek());
|
positionSlider.disabled = !((seekableDurationTicks || 0) > 0 || canPlayerSeek());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTimeElement) {
|
if (currentTimeElement) {
|
||||||
|
@ -655,7 +678,13 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
||||||
}
|
}
|
||||||
|
|
||||||
var onBitrateDetected = function () {
|
var onBitrateDetected = function () {
|
||||||
Dashboard.getDeviceProfile().then(function (deviceProfile) {
|
Dashboard.getDeviceProfile(null, {
|
||||||
|
|
||||||
|
enableMkvProgressive: item.RunTimeTicks != null,
|
||||||
|
enableTsProgressive: item.RunTimeTicks != null,
|
||||||
|
enableHls: !browserInfo.firefox || item.RunTimeTicks == null
|
||||||
|
|
||||||
|
}).then(function (deviceProfile) {
|
||||||
playOnDeviceProfileCreated(deviceProfile, item, startPosition, callback);
|
playOnDeviceProfileCreated(deviceProfile, item, startPosition, callback);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1516,7 +1545,6 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
||||||
Events.on(mediaRenderer, "timeupdate", onTimeUpdate);
|
Events.on(mediaRenderer, "timeupdate", onTimeUpdate);
|
||||||
|
|
||||||
self.currentMediaRenderer = mediaRenderer;
|
self.currentMediaRenderer = mediaRenderer;
|
||||||
self.currentDurationTicks = self.currentMediaSource.RunTimeTicks;
|
|
||||||
|
|
||||||
mediaRenderer.init().then(function () {
|
mediaRenderer.init().then(function () {
|
||||||
|
|
||||||
|
|
|
@ -670,7 +670,7 @@ var Dashboard = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getDeviceProfile: function (maxHeight) {
|
getDeviceProfile: function (maxHeight, profileOptions) {
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
|
|
||||||
|
@ -838,9 +838,9 @@ var Dashboard = {
|
||||||
supportsCustomSeeking = true;
|
supportsCustomSeeking = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var profile = profileBuilder({
|
var profile = profileBuilder(Object.assign(profileOptions || {}, {
|
||||||
supportsCustomSeeking: supportsCustomSeeking
|
supportsCustomSeeking: supportsCustomSeeking
|
||||||
});
|
}));
|
||||||
|
|
||||||
if (!(AppInfo.isNativeApp && browserInfo.android) && !browserInfo.edge && !browserInfo.msie) {
|
if (!(AppInfo.isNativeApp && browserInfo.android) && !browserInfo.edge && !browserInfo.msie) {
|
||||||
// libjass not working here
|
// libjass not working here
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<button is="emby-button" type="submit" class="raised button-submit block"><i class="md-icon">check</i><span>${ButtonSave}</span></button>
|
<button is="emby-button" type="submit" class="raised button-submit block"><span>${ButtonSave}</span></button>
|
||||||
<button is="emby-button" type="button" id="btnResetPassword" class="raised button-cancel block hide">
|
<button is="emby-button" type="button" id="btnResetPassword" class="raised button-cancel block hide">
|
||||||
<i class="md-icon">lock</i>
|
<i class="md-icon">lock</i>
|
||||||
<span>${ButtonResetPassword}</span>
|
<span>${ButtonResetPassword}</span>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue