mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge pull request #6196 from viown/prompt-to-skip
Add 'ask to skip' to media segments
This commit is contained in:
commit
fa1934a124
10 changed files with 277 additions and 25 deletions
|
@ -3,5 +3,6 @@
|
||||||
*/
|
*/
|
||||||
export enum MediaSegmentAction {
|
export enum MediaSegmentAction {
|
||||||
None = 'None',
|
None = 'None',
|
||||||
|
AskToSkip = 'AskToSkip',
|
||||||
Skip = 'Skip'
|
Skip = 'Skip'
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ export enum PlayerEvent {
|
||||||
PlaylistItemAdd = 'playlistitemadd',
|
PlaylistItemAdd = 'playlistitemadd',
|
||||||
PlaylistItemMove = 'playlistitemmove',
|
PlaylistItemMove = 'playlistitemmove',
|
||||||
PlaylistItemRemove = 'playlistitemremove',
|
PlaylistItemRemove = 'playlistitemremove',
|
||||||
|
PromptSkip = 'promptskip',
|
||||||
RepeatModeChange = 'repeatmodechange',
|
RepeatModeChange = 'repeatmodechange',
|
||||||
ShuffleModeChange = 'shufflequeuemodechange',
|
ShuffleModeChange = 'shufflequeuemodechange',
|
||||||
Stopped = 'stopped',
|
Stopped = 'stopped',
|
||||||
|
|
|
@ -37,6 +37,38 @@ class MediaSegmentManager extends PlaybackSubscriber {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skipSegment(mediaSegment: MediaSegmentDto) {
|
||||||
|
// Ignore segment if playback progress has passed the segment's start time
|
||||||
|
if (mediaSegment.StartTicks !== undefined && this.lastTime > mediaSegment.StartTicks) {
|
||||||
|
console.info('[MediaSegmentManager] ignoring skipping segment that has been seeked back into', mediaSegment);
|
||||||
|
this.isLastSegmentIgnored = true;
|
||||||
|
} else if (mediaSegment.EndTicks) {
|
||||||
|
// If there is an end time, seek to it
|
||||||
|
// Do not skip if duration < 1s to avoid slow stream changes
|
||||||
|
if (mediaSegment.StartTicks && mediaSegment.EndTicks - mediaSegment.StartTicks < TICKS_PER_SECOND) {
|
||||||
|
console.info('[MediaSegmentManager] ignoring skipping segment with duration <1s', mediaSegment);
|
||||||
|
this.isLastSegmentIgnored = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.debug('[MediaSegmentManager] skipping to %s ms', mediaSegment.EndTicks / TICKS_PER_MILLISECOND);
|
||||||
|
this.playbackManager.seek(mediaSegment.EndTicks, this.player);
|
||||||
|
} else {
|
||||||
|
// If there is no end time, skip to the next track
|
||||||
|
console.debug('[MediaSegmentManager] skipping to next item in queue');
|
||||||
|
this.playbackManager.nextTrack(this.player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
promptToSkip(mediaSegment: MediaSegmentDto) {
|
||||||
|
if (mediaSegment.StartTicks && mediaSegment.EndTicks
|
||||||
|
&& mediaSegment.EndTicks - mediaSegment.StartTicks < TICKS_PER_SECOND * 3) {
|
||||||
|
console.info('[MediaSegmentManager] ignoring segment prompt with duration <3s', mediaSegment);
|
||||||
|
this.isLastSegmentIgnored = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.playbackManager.promptToSkip(mediaSegment);
|
||||||
|
}
|
||||||
|
|
||||||
private performAction(mediaSegment: MediaSegmentDto) {
|
private performAction(mediaSegment: MediaSegmentDto) {
|
||||||
if (!this.mediaSegmentTypeActions || !mediaSegment.Type || !this.mediaSegmentTypeActions[mediaSegment.Type]) {
|
if (!this.mediaSegmentTypeActions || !mediaSegment.Type || !this.mediaSegmentTypeActions[mediaSegment.Type]) {
|
||||||
console.error('[MediaSegmentManager] segment type missing from action map', mediaSegment, this.mediaSegmentTypeActions);
|
console.error('[MediaSegmentManager] segment type missing from action map', mediaSegment, this.mediaSegmentTypeActions);
|
||||||
|
@ -45,27 +77,9 @@ class MediaSegmentManager extends PlaybackSubscriber {
|
||||||
|
|
||||||
const action = this.mediaSegmentTypeActions[mediaSegment.Type];
|
const action = this.mediaSegmentTypeActions[mediaSegment.Type];
|
||||||
if (action === MediaSegmentAction.Skip) {
|
if (action === MediaSegmentAction.Skip) {
|
||||||
// Ignore segment if playback progress has passed the segment's start time
|
this.skipSegment(mediaSegment);
|
||||||
if (mediaSegment.StartTicks !== undefined && this.lastTime > mediaSegment.StartTicks) {
|
} else if (action === MediaSegmentAction.AskToSkip) {
|
||||||
console.info('[MediaSegmentManager] ignoring skipping segment that has been seeked back into', mediaSegment);
|
this.promptToSkip(mediaSegment);
|
||||||
this.isLastSegmentIgnored = true;
|
|
||||||
return;
|
|
||||||
} else if (mediaSegment.EndTicks) {
|
|
||||||
// If there is an end time, seek to it
|
|
||||||
// Do not skip if duration < 1s to avoid slow stream changes
|
|
||||||
if (mediaSegment.StartTicks && mediaSegment.EndTicks - mediaSegment.StartTicks < TICKS_PER_SECOND) {
|
|
||||||
console.info('[MediaSegmentManager] ignoring skipping segment with duration <1s', mediaSegment);
|
|
||||||
this.isLastSegmentIgnored = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug('[MediaSegmentManager] skipping to %s ms', mediaSegment.EndTicks / TICKS_PER_MILLISECOND);
|
|
||||||
this.playbackManager.seek(mediaSegment.EndTicks, this.player);
|
|
||||||
} else {
|
|
||||||
// If there is no end time, skip to the next track
|
|
||||||
console.debug('[MediaSegmentManager] skipping to next item in queue');
|
|
||||||
this.playbackManager.nextTrack(this.player);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ const isBeforeSegment = (segment: MediaSegmentDto, time: number, direction: numb
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isInSegment = (segment: MediaSegmentDto, time: number) => (
|
export const isInSegment = (segment: MediaSegmentDto, time: number) => (
|
||||||
typeof segment.StartTicks !== 'undefined'
|
typeof segment.StartTicks !== 'undefined'
|
||||||
&& segment.StartTicks <= time
|
&& segment.StartTicks <= time
|
||||||
&& (typeof segment.EndTicks === 'undefined' || segment.EndTicks > time)
|
&& (typeof segment.EndTicks === 'undefined' || segment.EndTicks > time)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Events, { type Event } from 'utils/events';
|
||||||
import { PlaybackManagerEvent } from '../constants/playbackManagerEvent';
|
import { PlaybackManagerEvent } from '../constants/playbackManagerEvent';
|
||||||
import { PlayerEvent } from '../constants/playerEvent';
|
import { PlayerEvent } from '../constants/playerEvent';
|
||||||
import type { ManagedPlayerStopInfo, MovedItem, PlayerError, PlayerErrorCode, PlayerStopInfo, RemovedItems } from '../types/callbacks';
|
import type { ManagedPlayerStopInfo, MovedItem, PlayerError, PlayerErrorCode, PlayerStopInfo, RemovedItems } from '../types/callbacks';
|
||||||
|
import type { MediaSegmentDto } from '@jellyfin/sdk/lib/generated-client/models/media-segment-dto';
|
||||||
|
|
||||||
export interface PlaybackSubscriber {
|
export interface PlaybackSubscriber {
|
||||||
onPlaybackCancelled?(e: Event): void
|
onPlaybackCancelled?(e: Event): void
|
||||||
|
@ -18,6 +19,7 @@ export interface PlaybackSubscriber {
|
||||||
onPlaybackStart?(e: Event, player: Plugin, state: PlayerState): void
|
onPlaybackStart?(e: Event, player: Plugin, state: PlayerState): void
|
||||||
onPlaybackStop?(e: Event, info: PlaybackStopInfo): void
|
onPlaybackStop?(e: Event, info: PlaybackStopInfo): void
|
||||||
onPlayerChange?(e: Event, player: Plugin, target: PlayTarget, previousPlayer: Plugin): void
|
onPlayerChange?(e: Event, player: Plugin, target: PlayTarget, previousPlayer: Plugin): void
|
||||||
|
onPromptSkip?(e: Event, mediaSegment: MediaSegmentDto): void
|
||||||
onPlayerError?(e: Event, error: PlayerError): void
|
onPlayerError?(e: Event, error: PlayerError): void
|
||||||
onPlayerFullscreenChange?(e: Event): void
|
onPlayerFullscreenChange?(e: Event): void
|
||||||
onPlayerItemStarted?(e: Event, item?: BaseItemDto, mediaSource?: MediaSourceInfo): void
|
onPlayerItemStarted?(e: Event, item?: BaseItemDto, mediaSource?: MediaSourceInfo): void
|
||||||
|
@ -62,6 +64,7 @@ export abstract class PlaybackSubscriber {
|
||||||
[PlayerEvent.PlaylistItemAdd]: this.onPlayerPlaylistItemAdd?.bind(this),
|
[PlayerEvent.PlaylistItemAdd]: this.onPlayerPlaylistItemAdd?.bind(this),
|
||||||
[PlayerEvent.PlaylistItemMove]: this.onPlayerPlaylistItemMove?.bind(this),
|
[PlayerEvent.PlaylistItemMove]: this.onPlayerPlaylistItemMove?.bind(this),
|
||||||
[PlayerEvent.PlaylistItemRemove]: this.onPlayerPlaylistItemRemove?.bind(this),
|
[PlayerEvent.PlaylistItemRemove]: this.onPlayerPlaylistItemRemove?.bind(this),
|
||||||
|
[PlayerEvent.PromptSkip]: this.onPromptSkip?.bind(this),
|
||||||
[PlayerEvent.RepeatModeChange]: this.onPlayerRepeatModeChange?.bind(this),
|
[PlayerEvent.RepeatModeChange]: this.onPlayerRepeatModeChange?.bind(this),
|
||||||
[PlayerEvent.ShuffleModeChange]: this.onPlayerShuffleModeChange?.bind(this),
|
[PlayerEvent.ShuffleModeChange]: this.onPlayerShuffleModeChange?.bind(this),
|
||||||
[PlayerEvent.Stopped]: this.onPlayerStopped?.bind(this),
|
[PlayerEvent.Stopped]: this.onPlayerStopped?.bind(this),
|
||||||
|
|
|
@ -22,10 +22,13 @@ import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
|
||||||
import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdropImage';
|
import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdropImage';
|
||||||
|
|
||||||
import { bindMediaSegmentManager } from 'apps/stable/features/playback/utils/mediaSegmentManager';
|
import { bindMediaSegmentManager } from 'apps/stable/features/playback/utils/mediaSegmentManager';
|
||||||
|
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
|
||||||
import { MediaError } from 'types/mediaError';
|
import { MediaError } from 'types/mediaError';
|
||||||
import { getMediaError } from 'utils/mediaError';
|
import { getMediaError } from 'utils/mediaError';
|
||||||
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
||||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind.js';
|
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind.js';
|
||||||
|
import browser from 'scripts/browser.js';
|
||||||
|
import { bindSkipSegment } from './skipsegment.ts';
|
||||||
|
|
||||||
const UNLIMITED_ITEMS = -1;
|
const UNLIMITED_ITEMS = -1;
|
||||||
|
|
||||||
|
@ -933,6 +936,14 @@ export class PlaybackManager {
|
||||||
return Promise.resolve(self._playQueueManager.getPlaylist());
|
return Promise.resolve(self._playQueueManager.getPlaylist());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.promptToSkip = function (mediaSegment, player) {
|
||||||
|
player = player || self._currentPlayer;
|
||||||
|
|
||||||
|
if (mediaSegment && this._skipSegment) {
|
||||||
|
Events.trigger(player, PlayerEvent.PromptSkip, [mediaSegment]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function removeCurrentPlayer(player) {
|
function removeCurrentPlayer(player) {
|
||||||
const previousPlayer = self._currentPlayer;
|
const previousPlayer = self._currentPlayer;
|
||||||
|
|
||||||
|
@ -3676,6 +3687,9 @@ export class PlaybackManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
bindMediaSegmentManager(self);
|
bindMediaSegmentManager(self);
|
||||||
|
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||||
|
this._skipSegment = bindSkipSegment(self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentPlayer() {
|
getCurrentPlayer() {
|
||||||
|
@ -3690,6 +3704,10 @@ export class PlaybackManager {
|
||||||
return this.getCurrentTicks(player) / 10000;
|
return this.getCurrentTicks(player) / 10000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNextItem() {
|
||||||
|
return this._playQueueManager.getNextItemInfo();
|
||||||
|
}
|
||||||
|
|
||||||
nextItem(player = this._currentPlayer) {
|
nextItem(player = this._currentPlayer) {
|
||||||
if (player && !enableLocalPlaylistManagement(player)) {
|
if (player && !enableLocalPlaylistManagement(player)) {
|
||||||
return player.nextItem();
|
return player.nextItem();
|
||||||
|
|
32
src/components/playback/skipbutton.scss
Normal file
32
src/components/playback/skipbutton.scss
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
.skip-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 18%;
|
||||||
|
right: 16%;
|
||||||
|
z-index: 10000;
|
||||||
|
padding: 12px 20px;
|
||||||
|
color: black;
|
||||||
|
border: none;
|
||||||
|
border-radius: 100px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2em;
|
||||||
|
transition: opacity 200ms ease-out;
|
||||||
|
gap: 3px;
|
||||||
|
box-shadow: 7px 6px 15px -14px rgba(0, 0, 0, 0.65);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: landscape) and (max-height: 500px) {
|
||||||
|
.skip-button {
|
||||||
|
bottom: 27%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-transition {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-button-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
170
src/components/playback/skipsegment.ts
Normal file
170
src/components/playback/skipsegment.ts
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
import { PlaybackManager } from './playbackmanager';
|
||||||
|
import { TICKS_PER_MILLISECOND, TICKS_PER_SECOND } from 'constants/time';
|
||||||
|
import type { MediaSegmentDto } from '@jellyfin/sdk/lib/generated-client/models/media-segment-dto';
|
||||||
|
import { PlaybackSubscriber } from 'apps/stable/features/playback/utils/playbackSubscriber';
|
||||||
|
import { isInSegment } from 'apps/stable/features/playback/utils/mediaSegments';
|
||||||
|
import Events, { type Event } from '../../utils/events';
|
||||||
|
import { EventType } from 'types/eventType';
|
||||||
|
import './skipbutton.scss';
|
||||||
|
import dom from 'scripts/dom';
|
||||||
|
import globalize from 'lib/globalize';
|
||||||
|
|
||||||
|
interface ShowOptions {
|
||||||
|
animate?: boolean;
|
||||||
|
keep?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SkipSegment extends PlaybackSubscriber {
|
||||||
|
private skipElement: HTMLButtonElement | undefined;
|
||||||
|
private currentSegment: MediaSegmentDto | null | undefined;
|
||||||
|
private hideTimeout: ReturnType<typeof setTimeout> | null | undefined;
|
||||||
|
|
||||||
|
constructor(playbackManager: PlaybackManager) {
|
||||||
|
super(playbackManager);
|
||||||
|
|
||||||
|
this.onOsdChanged = this.onOsdChanged.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onHideComplete() {
|
||||||
|
if (this.skipElement) {
|
||||||
|
this.skipElement.classList.add('hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createSkipElement() {
|
||||||
|
if (!this.skipElement && this.currentSegment) {
|
||||||
|
const elem = document.createElement('button');
|
||||||
|
elem.classList.add('skip-button');
|
||||||
|
elem.classList.add('hide');
|
||||||
|
elem.classList.add('skip-button-hidden');
|
||||||
|
|
||||||
|
elem.addEventListener('click', () => {
|
||||||
|
const time = this.playbackManager.currentTime() * TICKS_PER_MILLISECOND;
|
||||||
|
if (this.currentSegment?.EndTicks) {
|
||||||
|
if (time < this.currentSegment.EndTicks - TICKS_PER_SECOND) {
|
||||||
|
this.playbackManager.seek(this.currentSegment.EndTicks);
|
||||||
|
} else {
|
||||||
|
this.hideSkipButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(elem);
|
||||||
|
this.skipElement = elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setButtonText() {
|
||||||
|
if (this.skipElement && this.currentSegment) {
|
||||||
|
this.skipElement.innerHTML = globalize.translate('MediaSegmentSkipPrompt', globalize.translate(`MediaSegmentType.${this.currentSegment.Type}`));
|
||||||
|
this.skipElement.innerHTML += '<span class="material-icons skip_next" aria-hidden="true"></span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showSkipButton(options: ShowOptions) {
|
||||||
|
const elem = this.skipElement;
|
||||||
|
if (elem) {
|
||||||
|
this.clearHideTimeout();
|
||||||
|
dom.removeEventListener(elem, dom.whichTransitionEvent(), this.onHideComplete, {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
elem.classList.remove('hide');
|
||||||
|
if (!options.animate) {
|
||||||
|
elem.classList.add('no-transition');
|
||||||
|
} else {
|
||||||
|
elem.classList.remove('no-transition');
|
||||||
|
}
|
||||||
|
|
||||||
|
void elem.offsetWidth;
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
elem.classList.remove('skip-button-hidden');
|
||||||
|
|
||||||
|
if (!options.keep) {
|
||||||
|
this.hideTimeout = setTimeout(this.hideSkipButton.bind(this), 8000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideSkipButton() {
|
||||||
|
const elem = this.skipElement;
|
||||||
|
if (elem) {
|
||||||
|
elem.classList.remove('no-transition');
|
||||||
|
void elem.offsetWidth;
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
elem.classList.add('skip-button-hidden');
|
||||||
|
|
||||||
|
dom.addEventListener(elem, dom.whichTransitionEvent(), this.onHideComplete, {
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearHideTimeout() {
|
||||||
|
if (this.hideTimeout) {
|
||||||
|
clearTimeout(this.hideTimeout);
|
||||||
|
this.hideTimeout = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOsdChanged(_e: Event, isOpen: boolean) {
|
||||||
|
if (this.currentSegment) {
|
||||||
|
if (isOpen) {
|
||||||
|
this.showSkipButton({
|
||||||
|
animate: false,
|
||||||
|
keep: true
|
||||||
|
});
|
||||||
|
} else if (!this.hideTimeout) {
|
||||||
|
this.hideSkipButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPromptSkip(e: Event, segment: MediaSegmentDto) {
|
||||||
|
if (this.player && segment.EndTicks != null
|
||||||
|
&& segment.EndTicks >= this.playbackManager.currentItem(this.player).RunTimeTicks
|
||||||
|
&& this.playbackManager.getNextItem()
|
||||||
|
) {
|
||||||
|
// Don't display button when UpNextDialog is expected.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.currentSegment) {
|
||||||
|
this.currentSegment = segment;
|
||||||
|
|
||||||
|
this.createSkipElement();
|
||||||
|
|
||||||
|
this.setButtonText();
|
||||||
|
|
||||||
|
this.showSkipButton({ animate: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlayerTimeUpdate() {
|
||||||
|
if (this.currentSegment) {
|
||||||
|
const time = this.playbackManager.currentTime(this.player) * TICKS_PER_MILLISECOND;
|
||||||
|
|
||||||
|
if (!isInSegment(this.currentSegment, time)) {
|
||||||
|
this.currentSegment = null;
|
||||||
|
this.hideSkipButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlayerChange(): void {
|
||||||
|
if (this.playbackManager.getCurrentPlayer()) {
|
||||||
|
Events.off(document, EventType.SHOW_VIDEO_OSD, this.onOsdChanged);
|
||||||
|
Events.on(document, EventType.SHOW_VIDEO_OSD, this.onOsdChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlaybackStop() {
|
||||||
|
this.currentSegment = null;
|
||||||
|
this.hideSkipButton();
|
||||||
|
Events.off(document, EventType.SHOW_VIDEO_OSD, this.onOsdChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const bindSkipSegment = (playbackManager: PlaybackManager) => new SkipSegment(playbackManager);
|
|
@ -29,9 +29,8 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
||||||
import { pluginManager } from '../../../components/pluginManager';
|
import { pluginManager } from '../../../components/pluginManager';
|
||||||
import { PluginType } from '../../../types/plugin.ts';
|
import { PluginType } from '../../../types/plugin.ts';
|
||||||
import { EventType } from 'types/eventType';
|
import { EventType } from 'types/eventType';
|
||||||
|
import { TICKS_PER_MINUTE, TICKS_PER_SECOND } from 'constants/time';
|
||||||
const TICKS_PER_MINUTE = 600000000;
|
import { PlayerEvent } from 'apps/stable/features/playback/constants/playerEvent';
|
||||||
const TICKS_PER_SECOND = 10000000;
|
|
||||||
|
|
||||||
function getOpenedDialog() {
|
function getOpenedDialog() {
|
||||||
return document.querySelector('.dialogContainer .dialog.opened');
|
return document.querySelector('.dialogContainer .dialog.opened');
|
||||||
|
@ -579,6 +578,7 @@ export default function (view) {
|
||||||
}, state);
|
}, state);
|
||||||
Events.on(player, 'playbackstart', onPlaybackStart);
|
Events.on(player, 'playbackstart', onPlaybackStart);
|
||||||
Events.on(player, 'playbackstop', onPlaybackStopped);
|
Events.on(player, 'playbackstop', onPlaybackStopped);
|
||||||
|
Events.on(player, PlayerEvent.PromptSkip, onPromptSkip);
|
||||||
Events.on(player, 'volumechange', onVolumeChanged);
|
Events.on(player, 'volumechange', onVolumeChanged);
|
||||||
Events.on(player, 'pause', onPlayPauseStateChanged);
|
Events.on(player, 'pause', onPlayPauseStateChanged);
|
||||||
Events.on(player, 'unpause', onPlayPauseStateChanged);
|
Events.on(player, 'unpause', onPlayPauseStateChanged);
|
||||||
|
@ -603,6 +603,7 @@ export default function (view) {
|
||||||
if (player) {
|
if (player) {
|
||||||
Events.off(player, 'playbackstart', onPlaybackStart);
|
Events.off(player, 'playbackstart', onPlaybackStart);
|
||||||
Events.off(player, 'playbackstop', onPlaybackStopped);
|
Events.off(player, 'playbackstop', onPlaybackStopped);
|
||||||
|
Events.off(player, PlayerEvent.PromptSkip, onPromptSkip);
|
||||||
Events.off(player, 'volumechange', onVolumeChanged);
|
Events.off(player, 'volumechange', onVolumeChanged);
|
||||||
Events.off(player, 'pause', onPlayPauseStateChanged);
|
Events.off(player, 'pause', onPlayPauseStateChanged);
|
||||||
Events.off(player, 'unpause', onPlayPauseStateChanged);
|
Events.off(player, 'unpause', onPlayPauseStateChanged);
|
||||||
|
@ -631,6 +632,16 @@ export default function (view) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onPromptSkip(e, mediaSegment) {
|
||||||
|
const player = this;
|
||||||
|
if (mediaSegment && player && mediaSegment.EndTicks != null
|
||||||
|
&& mediaSegment.EndTicks >= playbackManager.duration(player)
|
||||||
|
&& playbackManager.getNextItem()
|
||||||
|
) {
|
||||||
|
showComingUpNext(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showComingUpNextIfNeeded(player, currentItem, currentTimeTicks, runtimeTicks) {
|
function showComingUpNextIfNeeded(player, currentItem, currentTimeTicks, runtimeTicks) {
|
||||||
if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && currentItem.Type === 'Episode' && userSettings.enableNextVideoInfoOverlay()) {
|
if (runtimeTicks && currentTimeTicks && !comingUpNextDisplayed && !currentVisibleMenu && currentItem.Type === 'Episode' && userSettings.enableNextVideoInfoOverlay()) {
|
||||||
let showAtSecondsLeft = 30;
|
let showAtSecondsLeft = 30;
|
||||||
|
|
|
@ -1072,7 +1072,9 @@
|
||||||
"MediaInfoVideoRange": "Video range",
|
"MediaInfoVideoRange": "Video range",
|
||||||
"MediaIsBeingConverted": "The media is being converted into a format that is compatible with the device that is playing the media.",
|
"MediaIsBeingConverted": "The media is being converted into a format that is compatible with the device that is playing the media.",
|
||||||
"MediaSegmentAction.None": "None",
|
"MediaSegmentAction.None": "None",
|
||||||
|
"MediaSegmentAction.AskToSkip": "Ask To Skip",
|
||||||
"MediaSegmentAction.Skip": "Skip",
|
"MediaSegmentAction.Skip": "Skip",
|
||||||
|
"MediaSegmentSkipPrompt": "Skip {0}",
|
||||||
"MediaSegmentType.Commercial": "Commercial",
|
"MediaSegmentType.Commercial": "Commercial",
|
||||||
"MediaSegmentType.Intro": "Intro",
|
"MediaSegmentType.Intro": "Intro",
|
||||||
"MediaSegmentType.Outro": "Outro",
|
"MediaSegmentType.Outro": "Outro",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue