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;
|
||||
max-height: 84%;
|
||||
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 {
|
||||
|
|
|
@ -46,7 +46,7 @@ define(['browser'], function (browser) {
|
|||
}
|
||||
|
||||
function canPlayHlsWithMSE() {
|
||||
if (window.MediaSource != null && !browser.firefox) {
|
||||
if (window.MediaSource != null) {
|
||||
// text tracks don’t work with this in firefox
|
||||
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
|
||||
if (canPlayMkv && options.supportsCustomSeeking && !browser.tizen) {
|
||||
if (canPlayMkv && options.supportsCustomSeeking && !browser.tizen && options.enableMkvProgressive !== false) {
|
||||
profile.TranscodingProfiles.push({
|
||||
Container: 'mkv',
|
||||
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({
|
||||
Container: 'ts',
|
||||
Type: 'Video',
|
||||
|
@ -414,7 +414,7 @@ define(['browser'], function (browser) {
|
|||
});
|
||||
}
|
||||
|
||||
if (canPlayHls()) {
|
||||
if (canPlayHls() && options.enableHls !== false) {
|
||||
profile.TranscodingProfiles.push({
|
||||
Container: 'ts',
|
||||
Type: 'Video',
|
||||
|
|
|
@ -30,15 +30,18 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're
|
|||
formDialogContent.style['max-height'] = '60%';
|
||||
scrollHelper.centerFocus.on(formDialogContent, false);
|
||||
} else {
|
||||
var minWidth = (Math.min(options.buttons.length * 150, dom.getWindowSize().innerWidth - 50));
|
||||
dlg.style.maxWidth = (minWidth + 200) + 'px';
|
||||
dlg.style.maxWidth = (Math.min((options.buttons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)) + 'px';
|
||||
}
|
||||
|
||||
//dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
|
||||
// dialogHelper.close(dlg);
|
||||
//});
|
||||
|
||||
if (options.title) {
|
||||
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title || '';
|
||||
} else {
|
||||
dlg.querySelector('.formDialogHeaderTitle').classList.add('hide');
|
||||
}
|
||||
|
||||
dlg.querySelector('.text').innerHTML = options.html || options.text || '';
|
||||
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
margin: 0;
|
||||
border-radius: 4px;
|
||||
-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;
|
||||
padding: 0;
|
||||
will-change: transform;
|
||||
/* Strict does not work well with actionsheet */
|
||||
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 {
|
||||
|
@ -37,6 +37,7 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@media all and (max-width: 1280px), all and (max-height: 720px) {
|
||||
|
@ -47,6 +48,7 @@
|
|||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
margin: 0 !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<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 is="emby-button" type="button" id="btnResetEasyPassword" class="raised cancel block hide">
|
||||
<i class="md-icon">lock</i>
|
||||
|
|
|
@ -328,8 +328,6 @@
|
|||
|
||||
$('.sliderValue', elem).html(tooltext);
|
||||
|
||||
console.log("slidin", pct, self.currentDurationTicks, time);
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -253,7 +253,10 @@
|
|||
self.duration = function (val) {
|
||||
|
||||
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;
|
||||
|
|
|
@ -386,19 +386,15 @@
|
|||
name: Globalize.translate('ButtonNo'),
|
||||
id: 'no'
|
||||
});
|
||||
menuItems.push({
|
||||
name: Globalize.translate('ButtonCancel'),
|
||||
id: 'cancel'
|
||||
});
|
||||
|
||||
require(['actionsheet'], function (actionsheet) {
|
||||
require(['dialog'], function (dialog) {
|
||||
|
||||
actionsheet.show({
|
||||
items: menuItems,
|
||||
dialog({
|
||||
buttons: menuItems,
|
||||
//positionTo: positionTo,
|
||||
title: Globalize.translate('ConfirmEndPlayerSession'),
|
||||
callback: function (id) {
|
||||
text: Globalize.translate('ConfirmEndPlayerSession')
|
||||
|
||||
}).then(function (id) {
|
||||
switch (id) {
|
||||
|
||||
case 'yes':
|
||||
|
@ -411,7 +407,6 @@
|
|||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -595,24 +595,11 @@
|
|||
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() {
|
||||
|
||||
var newPercent = parseFloat(this.value);
|
||||
|
||||
var newPositionTicks = (newPercent / 100) * getSeekableDuration();
|
||||
var newPositionTicks = (newPercent / 100) * self.getSeekableDurationTicks();
|
||||
|
||||
self.changeStream(Math.floor(newPositionTicks));
|
||||
}
|
||||
|
@ -808,7 +795,7 @@
|
|||
|
||||
positionSlider.getBubbleText = function (value) {
|
||||
|
||||
var seekableDuration = getSeekableDuration();
|
||||
var seekableDuration = self.getSeekableDurationTicks();
|
||||
if (!self.currentMediaSource || !seekableDuration) {
|
||||
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
|
||||
// This will start the transcoding process before actually feeding the video url into the player
|
||||
// Edit: Also seeing stalls from hls.js
|
||||
if (!mediaSource.RunTimeTicks && isHls) {
|
||||
if (!mediaSource.RunTimeTicks && isHls && !browserInfo.edge) {
|
||||
|
||||
Dashboard.showLoadingMsg();
|
||||
var hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8');
|
||||
|
@ -1170,7 +1157,6 @@
|
|||
document.body.classList.add('bodyWithPopupOpen');
|
||||
|
||||
self.currentMediaRenderer = mediaRenderer;
|
||||
self.currentDurationTicks = self.currentMediaSource.RunTimeTicks;
|
||||
|
||||
self.updateNowPlayingInfo(item);
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
|||
self.currentItem = null;
|
||||
self.currentMediaSource = null;
|
||||
|
||||
self.currentDurationTicks = null;
|
||||
self.startTimeTicksOffset = null;
|
||||
|
||||
self.playlist = [];
|
||||
|
@ -146,7 +145,7 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
|||
|
||||
// viblast can help us here
|
||||
//return true;
|
||||
return window.MediaSource && !browserInfo.firefox;
|
||||
return window.MediaSource;
|
||||
};
|
||||
|
||||
self.changeStream = function (ticks, params) {
|
||||
|
@ -166,7 +165,13 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
|||
var playSessionId = getParameterByName('PlaySessionId', 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;
|
||||
if (typeof (audioStreamIndex) == 'string') {
|
||||
|
@ -264,6 +269,22 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
|||
//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) {
|
||||
|
||||
// Convert to ticks
|
||||
|
@ -272,13 +293,15 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
|||
var timeText = datetime.getDisplayRunningTime(ticks);
|
||||
var mediaRenderer = self.currentMediaRenderer;
|
||||
|
||||
if (self.currentDurationTicks) {
|
||||
var seekableDurationTicks = self.getSeekableDurationTicks();
|
||||
|
||||
timeText += " / " + datetime.getDisplayRunningTime(self.currentDurationTicks);
|
||||
if (seekableDurationTicks) {
|
||||
|
||||
timeText += " / " + datetime.getDisplayRunningTime(seekableDurationTicks);
|
||||
|
||||
if (positionSlider) {
|
||||
|
||||
var percent = ticks / self.currentDurationTicks;
|
||||
var percent = ticks / seekableDurationTicks;
|
||||
percent *= 100;
|
||||
|
||||
positionSlider.value = percent;
|
||||
|
@ -287,7 +310,7 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
|||
|
||||
if (positionSlider) {
|
||||
|
||||
positionSlider.disabled = !((self.currentDurationTicks || 0) > 0 || canPlayerSeek());
|
||||
positionSlider.disabled = !((seekableDurationTicks || 0) > 0 || canPlayerSeek());
|
||||
}
|
||||
|
||||
if (currentTimeElement) {
|
||||
|
@ -655,7 +678,13 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
|||
}
|
||||
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
@ -1516,7 +1545,6 @@ define(['appSettings', 'userSettings', 'appStorage', 'datetime'], function (appS
|
|||
Events.on(mediaRenderer, "timeupdate", onTimeUpdate);
|
||||
|
||||
self.currentMediaRenderer = mediaRenderer;
|
||||
self.currentDurationTicks = self.currentMediaSource.RunTimeTicks;
|
||||
|
||||
mediaRenderer.init().then(function () {
|
||||
|
||||
|
|
|
@ -670,7 +670,7 @@ var Dashboard = {
|
|||
});
|
||||
},
|
||||
|
||||
getDeviceProfile: function (maxHeight) {
|
||||
getDeviceProfile: function (maxHeight, profileOptions) {
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
|
@ -838,9 +838,9 @@ var Dashboard = {
|
|||
supportsCustomSeeking = true;
|
||||
}
|
||||
|
||||
var profile = profileBuilder({
|
||||
var profile = profileBuilder(Object.assign(profileOptions || {}, {
|
||||
supportsCustomSeeking: supportsCustomSeeking
|
||||
});
|
||||
}));
|
||||
|
||||
if (!(AppInfo.isNativeApp && browserInfo.android) && !browserInfo.edge && !browserInfo.msie) {
|
||||
// libjass not working here
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
</div>
|
||||
<br />
|
||||
<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">
|
||||
<i class="md-icon">lock</i>
|
||||
<span>${ButtonResetPassword}</span>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue