Skip to content

Commit

Permalink
Merge pull request #56 from Uscreen-video/resolve-subtitles-issues
Browse files Browse the repository at this point in the history
Resolve subtitles issues
  • Loading branch information
timaramazanov authored Oct 23, 2024
2 parents 616e009 + 690782f commit c0a83d1
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 56 deletions.
22 changes: 11 additions & 11 deletions src/components/buttons/Subtitles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ const icons = {

@customElement("video-subtitles-button")
export class SubtitlesButton extends VideoButton {
@connect("activeTextTrack")
activeTrack: string;
@connect("activeTextTrackId")
activeTrackId: string;

@connect("textTracks")
textTracks: State["textTracks"];
Expand All @@ -38,8 +38,8 @@ export class SubtitlesButton extends VideoButton {
if (!this.textTracks?.length) return null;

return html`
<slot name="icon:${this.activeTrack ? "enabled" : "disabled"}">
${this.activeTrack ? icons.solid : icons.outline}
<slot name="icon:${this.activeTrackId ? "enabled" : "disabled"}">
${this.activeTrackId ? icons.solid : icons.outline}
</slot>
`;
}
Expand Down Expand Up @@ -76,21 +76,21 @@ export class SubtitlesButton extends VideoButton {
};

handleItemClick = (e: any) => {
const lang = e.detail.value;
const trackId = e.detail.value;
this.command(Types.Command.enableTextTrack, {
lang: lang === "off" ? "" : lang,
trackId: trackId === "off" ? "" : trackId,
});
this.removeMenu();
};

get getMenuItems(): any {
const active = this.activeTrack || "";
return [{ label: "Off", lang: "" }, ...(this.textTracks || [])].map(
const active = this.activeTrackId || "off";
return [{ label: "Off", lang: "", id: "off" }, ...(this.textTracks || [])].map(
(track) => ({
...track,
value: track.lang || "off",
isActive: active === track.lang,
iconAfter: active === track.lang ? icons.check : null,
value: track.id,
isActive: active === track.id,
iconAfter: active === track.id ? icons.check : null,
}),
);
}
Expand Down
14 changes: 8 additions & 6 deletions src/components/video-button/Video-button.styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
--menu-item-color-active: $menu-item-color-active;
--menu-item-background-hover: var(--primary, $menu-item-background-hover);
--menu-item-color-hover: $menu-item-color-hover;
--menu-max-height: auto;
--menu-max-height: var(--video-menu-max-height, auto);
--menu-max-height-subtract: 110px;

--size: var(--button-size);
}
Expand Down Expand Up @@ -72,10 +73,10 @@ button {
white-space: nowrap;
background: var(--tooltip-background);
height: 0;
overflow: hidden;
user-select: none;
max-height: var(--menu-max-height);
overflow: auto;
max-height: calc(var(--menu-max-height) - var(--menu-max-height-subtract));
overflow-x: hidden;
overflow-y: auto;
& .inner {
padding: 6px 12px;
}
Expand All @@ -85,8 +86,9 @@ button {
}

@media (max-width: 640px) {
:host(:not([is-fullscreen])) .menu {
--menu-max-height: 150px;
.tooltip,
.menu {
--menu-max-height-subtract: 90px;
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/components/video-chromecast/Video-chromecast.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export class VideoChromecast extends LitElement {
@connect("textTracks")
cues: State["textTracks"];

@connect("activeTextTrack")
activeTextTrack: string;
@connect("activeTextTrackId")
activeTextTrackId: string;

@state()
targetDevise: string;
Expand Down Expand Up @@ -100,7 +100,7 @@ export class VideoChromecast extends LitElement {

if (!media) return;

const index = this.cues.findIndex((s) => this.activeTextTrack === s.lang);
const index = this.cues.findIndex((s) => this.activeTextTrackId === s.id);
const request = new window.chrome.cast.media.EditTracksInfoRequest([index]);

media.editTracksInfo(request, void 0, void 0);
Expand Down Expand Up @@ -148,9 +148,9 @@ export class VideoChromecast extends LitElement {

const request = new window.chrome.cast.media.LoadRequest(media);

if (this.activeTextTrack) {
if (this.activeTextTrackId) {
const subtitlesLanguageIdx = this.cues.findIndex(
({ lang }) => this.activeTextTrack === lang,
({ id }) => this.activeTextTrackId === id,
);

request.activeTrackIds =
Expand Down
14 changes: 7 additions & 7 deletions src/components/video-container/Video-container.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,11 @@ export class VideoContainer extends LitElement {
}

@listen(Types.Command.enableTextTrack)
enableTextTrack({ lang }: { lang: string }) {
enableTextTrack({ trackId }: { trackId: string }) {
dispatch(this, Types.Action.selectTextTrack, {
activeTextTrack: lang,
activeTextTrackId: trackId,
});
this.subtitles.enableTextTrack(lang);
this.subtitles.enableTextTrack(trackId);
}

@listen(Types.Command.setPlaybackRate, { canPlay: true })
Expand Down Expand Up @@ -340,7 +340,7 @@ export class VideoContainer extends LitElement {
this,
this.videos[0],
this.hls,
this._storageProvider.get().activeTextTrack,
this._storageProvider.get().activeTextTrackId,
);
},
);
Expand Down Expand Up @@ -463,8 +463,8 @@ export class VideoContainer extends LitElement {
value = +params.playbackRate;
break;
case Types.Command.enableTextTrack:
key = "activeTextTrack";
value = params.lang;
key = "activeTextTrackId";
value = params.trackId;
break;
}
const currentVal = this._storageProvider.get();
Expand Down Expand Up @@ -494,7 +494,7 @@ export class VideoContainer extends LitElement {
this,
this.videos[0],
this.hls,
savedSettings.activeTextTrack,
savedSettings.activeTextTrackId,
);
}

Expand Down
51 changes: 30 additions & 21 deletions src/components/video-container/subtitles.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { dispatch, Types } from "../../state";
import { VideoContainer } from "./Video-container.component";
import { mapCueListToState } from "../../helpers/cue";
import debounce from "../../helpers/debounce"
import type Hls from "hls.js";
import _debug from "debug";
import { MediaPlaylist } from "hls.js";

const subtitlesDebug = _debug("player:subtitles");

const buildTrackId = (track: TextTrack) => `${track.label}-${track.language}`;
const buildHlsTrackId = (track: MediaPlaylist) => `${track.name}-${track.lang}`;

/**
* Util to manage vide text tracks
*/
Expand Down Expand Up @@ -34,22 +39,21 @@ const videoTextTtracksManager = (video: HTMLVideoElement, hls: Hls) => {
src: langToSrcMapping[t.language] || "",
lang: t.language || t.label,
label: t.label,
})),
id: buildTrackId(t),
})).sort((a, b) => a.lang.localeCompare(b.lang, undefined, { sensitivity: 'base' }))
});

const showTracks = (lang: string) => {
const showTracks = (trackId: string) => {
if (hls) {
hls.subtitleTracks.forEach((t) => {
const tLang = t.lang || t.name;
if (tLang === lang) {
if (buildHlsTrackId(t) === trackId) {
hls.subtitleTrack = t.id;
hls.subtitleDisplay = true;
}
});
}
getTracks().forEach((t) => {
const tLang = t.language || t.label;
if (tLang === lang) {
if (buildTrackId(t) === trackId) {
t.mode = "hidden";
} else {
t.mode = "disabled";
Expand Down Expand Up @@ -77,23 +81,22 @@ export const subtitlesController = (
host: VideoContainer,
video: HTMLVideoElement,
hls: Hls,
defaultTextTrack?: string,
defaultTextTrackId?: string,
) => {
if (hls) {
// Disable subtitles by default
hls.subtitleTrack = -1;
hls.subtitleDisplay = false;
}

let activeTextTrack = defaultTextTrack;
let activeTextTrackId = defaultTextTrackId;

const tracksManager = videoTextTtracksManager(video, hls);

if (tracksManager.hasNonNative()) {
tracksManager.removeNativeTextTracks()
}

dispatch(host, Types.Action.update, tracksManager.tracksToStoreState());

const onCueChange = (event: Event & { target: TextTrack }) => {
subtitlesDebug(
Expand All @@ -102,16 +105,16 @@ export const subtitlesController = (
event.target.kind,
event.target.mode,
);
const targetLang = event.target.language || event.target.label;
const targetTrackId = buildTrackId(event.target)

if (event.target.mode === "showing" && targetLang !== activeTextTrack) {
activeTextTrack = targetLang;
if (event.target.mode === "showing" && targetTrackId !== activeTextTrackId) {
activeTextTrackId = targetTrackId;
dispatch(host, Types.Action.selectTextTrack, {
activeTextTrack: targetLang,
activeTextTrackId: targetTrackId,
});
}

if (targetLang === activeTextTrack) {
if (targetTrackId === activeTextTrackId) {
const cues = mapCueListToState(event.target.activeCues);
dispatch(host, Types.Action.cues, { cues });
}
Expand All @@ -121,7 +124,13 @@ export const subtitlesController = (
t.oncuechange = onCueChange;
});

tracksManager.showTracks(activeTextTrack);
tracksManager.showTracks(activeTextTrackId);

const updateTracksListSate = debounce(() => {
dispatch(host, Types.Action.update, tracksManager.tracksToStoreState());
}, 100)

updateTracksListSate()

const onTextTrackAdded = (data: TrackEvent) => {
subtitlesDebug(
Expand All @@ -133,20 +142,20 @@ export const subtitlesController = (
if (!tracksManager.isTrackNative(data.track)) {
tracksManager.removeNativeTextTracks()
data.track.oncuechange = onCueChange;
tracksManager.showTracks(activeTextTrack);
dispatch(host, Types.Action.update, tracksManager.tracksToStoreState());
tracksManager.showTracks(activeTextTrackId);
updateTracksListSate()
}
};

video.textTracks.addEventListener("addtrack", onTextTrackAdded);

return {
enableTextTrack: (lang: string) => {
activeTextTrack = lang;
tracksManager.showTracks(activeTextTrack);
enableTextTrack: (trackId: string) => {
activeTextTrackId = trackId;
tracksManager.showTracks(activeTextTrackId);
const activeTrack = tracksManager
.getTracks()
.find((t) => (t.language || t.label) === activeTextTrack);
.find((t) => buildTrackId(t) === activeTextTrackId);
if (activeTrack && activeTrack.activeCues) {
dispatch(host, Types.Action.cues, {
cues: mapCueListToState(activeTrack.activeCues),
Expand Down
24 changes: 23 additions & 1 deletion src/components/video-controls/Video-controls.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { connect } from "../../state";
import { unsafeCSS, LitElement, html } from "lit";
import { unsafeCSS, LitElement, html, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";
import { DependentPropsMixin } from "../../mixins/DependentProps";
import styles from "./Video-controls.styles.css?inline";
Expand Down Expand Up @@ -39,6 +39,28 @@ export class VideoControls extends DependentPropsMixin(LitElement) {
@property({ type: Boolean, reflect: true })
custom = false;

private resizeObserver: ResizeObserver;

connectedCallback(): void {
super.connectedCallback();
this.resizeObserver = new ResizeObserver((entries) => {
const [entry] = entries
if (entry?.contentBoxSize) {
const { blockSize } = entry.contentBoxSize[0]
this.style.cssText = `${this.style.cssText}; --video-menu-max-height: ${Math.round(blockSize)}px;`
}
});
}

protected firstUpdated(_changedProperties: PropertyValues): void {
this.resizeObserver.observe(this.parentElement)
}

disconnectedCallback(): void {
super.disconnectedCallback()
this.resizeObserver?.disconnect()
}

render() {
return html`<slot></slot>`;
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/video-cues/Video-cues.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export class VideoCues extends LitElement {
/**
* The currently active text track (e.g., subtitles or captions).
*/
@connect("activeTextTrack")
activeTextTrack: string;
@connect("activeTextTrackId")
activeTextTrackId: string;

/**
* An array of cues or subtitles to be displayed during video playback.
Expand All @@ -41,7 +41,7 @@ export class VideoCues extends LitElement {
isFullscreen: false;

render() {
if ((this.isIos && this.isFullscreen) || !this.activeTextTrack) return null;
if ((this.isIos && this.isFullscreen) || !this.activeTextTrackId) return null;

return this.cues.map(
(cue) => html`
Expand Down
14 changes: 14 additions & 0 deletions src/helpers/debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void {
let timeout: ReturnType<typeof setTimeout> | null;

return function(...args: Parameters<T>): void {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
func(...args);
}, wait);
};
}

export default debounce;
2 changes: 1 addition & 1 deletion src/helpers/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type StorageValue = Partial<
| "isMuted"
| "volume"
| "activeQualityLevel"
| "activeTextTrack"
| "activeTextTrackId"
| "playbackRate"
>
>;
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export type State = Partial<
isAutoplay: boolean;
isSourceSupported: boolean;
isFullscreen: boolean;
activeTextTrack: string;
activeTextTrackId: string;
activeQualityLevel: number;
playbackRate: number;
customHLS: boolean;
Expand All @@ -132,6 +132,7 @@ export type State = Partial<
label: string;
src: string;
lang: string;
id: string;
}[];
qualityLevels: {
name: string;
Expand Down

0 comments on commit c0a83d1

Please sign in to comment.