From dd29347af831dbbad69279de58456f991b0b04af Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Wed, 18 Sep 2024 18:17:22 +0100 Subject: [PATCH 01/48] Feat: Support MSE Subtitles for Low Latency --- src/playbackstrategy/msestrategy.js | 29 +++++++++++++++++- src/playercomponent.js | 5 ++++ src/subtitles/dashsubtitles.js | 46 +++++++++++++++++++++++++++++ src/subtitles/subtitles.js | 12 ++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/subtitles/dashsubtitles.js diff --git a/src/playbackstrategy/msestrategy.js b/src/playbackstrategy/msestrategy.js index 0aaa603c1..895782060 100644 --- a/src/playbackstrategy/msestrategy.js +++ b/src/playbackstrategy/msestrategy.js @@ -93,6 +93,7 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD STREAM_INITIALIZED: "streamInitialized", FRAGMENT_CONTENT_LENGTH_MISMATCH: "fragmentContentLengthMismatch", QUOTA_EXCEEDED: "quotaExceeded", + TEXT_TRACKS_ADDED: "allTextTracksAdded", } function onLoadedMetaData() { @@ -491,10 +492,16 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD function setUpMediaPlayer(playbackTime) { const dashSettings = getDashSettings(playerSettings) + const dashSubs = window.bigscreenPlayer?.overrides?.dashSubtitles ?? false mediaPlayer = MediaPlayer().create() mediaPlayer.updateSettings(dashSettings) mediaPlayer.initialize(mediaElement, null, true) + + if (dashSubs) { + mediaPlayer.attachTTMLRenderingDiv(document.querySelector("#bsp_subtitles")) + } + modifySource(playbackTime) } @@ -504,7 +511,6 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD windowType, initialSeekableRangeStartSeconds: mediaSources.time().windowStartTime / 1000, }) - mediaPlayer.attachSource(`${source}${anchor}`) } @@ -540,6 +546,21 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD mediaPlayer.on(DashJSEvents.GAP_JUMP, onGapJump) mediaPlayer.on(DashJSEvents.GAP_JUMP_TO_END, onGapJump) mediaPlayer.on(DashJSEvents.QUOTA_EXCEEDED, onQuotaExceeded) + mediaPlayer.on(DashJSEvents.TEXT_TRACKS_ADDED, disableTextTracks) + } + + function disableTextTracks() { + const textTracks = mediaElement.textTracks + for (let index = 0; index < textTracks.length; index++) { + textTracks[index].mode = "disabled" + } + } + + function enableTextTracks() { + const textTracks = mediaElement.textTracks + for (let index = 0; index < textTracks.length; index++) { + textTracks[index].mode = "showing" + } } function getSeekableRange() { @@ -645,6 +666,12 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD getSeekableRange, getCurrentTime, getDuration, + setSubtitles: (state) => { + if (state) { + enableTextTracks() + } + mediaPlayer.enableText(state) + }, getPlayerElement: () => mediaElement, tearDown: () => { mediaPlayer.reset() diff --git a/src/playercomponent.js b/src/playercomponent.js index 54784ace0..b7657c9be 100644 --- a/src/playercomponent.js +++ b/src/playercomponent.js @@ -71,6 +71,10 @@ function PlayerComponent( } } + function setSubtitles(state) { + return playbackStrategy && playbackStrategy.setSubtitles(state) + } + function getDuration() { return playbackStrategy && playbackStrategy.getDuration() } @@ -394,6 +398,7 @@ function PlayerComponent( return { play, pause, + setSubtitles, transitions, isEnded, setPlaybackRate, diff --git a/src/subtitles/dashsubtitles.js b/src/subtitles/dashsubtitles.js new file mode 100644 index 000000000..7847e3527 --- /dev/null +++ b/src/subtitles/dashsubtitles.js @@ -0,0 +1,46 @@ +function DashSubtitles(mediaPlayer, autoStart, parentElement) { + let currentSubtitlesElement + + if (autoStart) { + start() + } + + function removeCurrentSubtitlesElement() { + if (currentSubtitlesElement) { + DOMHelpers.safeRemoveElement(currentSubtitlesElement) + currentSubtitlesElement = undefined + } + } + + function addCurrentSubtitlesElement() { + removeCurrentSubtitlesElement() + currentSubtitlesElement = document.createElement("div") + currentSubtitlesElement.id = "bsp_subtitles" + currentSubtitlesElement.style.position = "absolute" + parentElement.appendChild(currentSubtitlesElement) + } + + function start() { + mediaPlayer.setSubtitles(true) + if (!currentSubtitlesElement) { + addCurrentSubtitlesElement() + } + } + + function stop() { + mediaPlayer.setSubtitles(false) + } + + addCurrentSubtitlesElement() + + return { + start, + stop, + customise: () => {}, + tearDown: () => { + stop() + }, + } +} + +export default DashSubtitles diff --git a/src/subtitles/subtitles.js b/src/subtitles/subtitles.js index 1ffff7a40..2d33d9a21 100644 --- a/src/subtitles/subtitles.js +++ b/src/subtitles/subtitles.js @@ -3,6 +3,8 @@ import findSegmentTemplate from "../utils/findtemplate" function Subtitles(mediaPlayer, autoStart, playbackElement, defaultStyleOpts, mediaSources, callback) { const useLegacySubs = window.bigscreenPlayer?.overrides?.legacySubtitles ?? false + const dashSubs = window.bigscreenPlayer?.overrides?.dashSubtitles ?? false + const isSeekableLiveSupport = window.bigscreenPlayer.liveSupport == null || window.bigscreenPlayer.liveSupport === "seekable" @@ -19,6 +21,16 @@ function Subtitles(mediaPlayer, autoStart, playbackElement, defaultStyleOpts, me .catch(() => { Plugins.interface.onSubtitlesDynamicLoadError() }) + } + if (dashSubs) { + import("./dashsubtitles.js") + .then(({ default: DashSubtitles }) => { + subtitlesContainer = DashSubtitles(mediaPlayer, autoStart, playbackElement, mediaSources, defaultStyleOpts) + callback(subtitlesEnabled) + }) + .catch(() => { + Plugins.interface.onSubtitlesDynamicLoadError() + }) } else { import("./imscsubtitles.js") .then(({ default: IMSCSubtitles }) => { From f9b1227e90559dad310f03304ca9088fa6757af2 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Wed, 18 Sep 2024 22:32:54 +0100 Subject: [PATCH 02/48] Add tests for new subtitle type --- src/subtitles/subtitles.js | 5 ++--- src/subtitles/subtitles.test.js | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/subtitles/subtitles.js b/src/subtitles/subtitles.js index 2d33d9a21..1580f4e56 100644 --- a/src/subtitles/subtitles.js +++ b/src/subtitles/subtitles.js @@ -21,8 +21,7 @@ function Subtitles(mediaPlayer, autoStart, playbackElement, defaultStyleOpts, me .catch(() => { Plugins.interface.onSubtitlesDynamicLoadError() }) - } - if (dashSubs) { + } else if (dashSubs) { import("./dashsubtitles.js") .then(({ default: DashSubtitles }) => { subtitlesContainer = DashSubtitles(mediaPlayer, autoStart, playbackElement, mediaSources, defaultStyleOpts) @@ -87,7 +86,7 @@ function Subtitles(mediaPlayer, autoStart, playbackElement, defaultStyleOpts, me const isWhole = findSegmentTemplate(url) == null - return isWhole || (!useLegacySubs && isSeekableLiveSupport) + return isWhole || (!useLegacySubs && !dashSubs && isSeekableLiveSupport) } function setPosition(position) { diff --git a/src/subtitles/subtitles.test.js b/src/subtitles/subtitles.test.js index 250a78dc2..c6d140b5d 100644 --- a/src/subtitles/subtitles.test.js +++ b/src/subtitles/subtitles.test.js @@ -1,10 +1,13 @@ /* eslint-disable jest/no-done-callback */ import IMSCSubtitles from "./imscsubtitles" import LegacySubtitles from "./legacysubtitles" +import DashSubtitles from "./dashsubtitles" + import Subtitles from "./subtitles" jest.mock("./imscsubtitles") jest.mock("./legacysubtitles") +jest.mock("./dashsubtitles") describe("Subtitles", () => { let isAvailable @@ -74,6 +77,42 @@ describe("Subtitles", () => { }) }) + describe("dash", () => { + beforeEach(() => { + window.bigscreenPlayer = { + overrides: { + dashSubtitles: true, + }, + } + + DashSubtitles.mockReset() + }) + + it("implementation is available when dash subtitles override is true", (done) => { + const mockMediaPlayer = {} + const autoStart = true + + Subtitles(mockMediaPlayer, autoStart, playbackElement, null, mockMediaSources, (result) => { + expect(result).toBe(true) + expect(DashSubtitles).toHaveBeenCalledTimes(1) + done() + }) + }) + + it("implementation is not available when dash subtitles override is true, but subtitles are segmented", (done) => { + isSegmented = true + const mockMediaPlayer = {} + const autoStart = true + + Subtitles(mockMediaPlayer, autoStart, playbackElement, null, mockMediaSources, () => { + expect(LegacySubtitles).not.toHaveBeenCalled() + expect(IMSCSubtitles).not.toHaveBeenCalled() + expect(DashSubtitles).not.toHaveBeenCalled() + done() + }) + }) + }) + describe("imscjs", () => { it("implementation is available when legacy subtitles override is false", (done) => { const mockMediaPlayer = {} From 80d4851d28c86259f5739d9d34057910b04e6741 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Thu, 19 Sep 2024 09:20:23 +0100 Subject: [PATCH 03/48] Subtitles: low-latency docs --- docs/tutorials/Subtitles.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/tutorials/Subtitles.md b/docs/tutorials/Subtitles.md index a97471589..50bdf181d 100644 --- a/docs/tutorials/Subtitles.md +++ b/docs/tutorials/Subtitles.md @@ -10,10 +10,12 @@ You provide subtitles to BigscreenPlayer by setting `media.captions` in the `.in ```js // 1️⃣ Add an array of caption blocks to your playback data. -playbackData.media.captions = [/* caption blocks... */]; +playbackData.media.captions = [ + /* caption blocks... */ +] // 2️⃣ Pass playback data that contains captions to the player. -player.init(document.querySelector("video"), playbackData, /* other opts */); +player.init(document.querySelector("video"), playbackData /* other opts */) ``` 1. `media.captions` MUST be an array containing at least one object. @@ -34,7 +36,7 @@ const captions = [ { url: "https://some.cdn/subtitles.xml" }, { url: "https://other.cdn/subtitles.xml" }, /* ... */ -]; +] ``` Subtitles delivered as a whole do not require any additional metadata in the manifest to work. @@ -46,7 +48,7 @@ Subtitles are delivered "as segments" when the captions' `url` is an URL templat ```js // Each segment specifies subtitles for a segment of the media experience. const captions = [ - { + { url: "https://some.cdn/subtitles/$segment$.m4s", segmentLength: 3.84, }, @@ -55,7 +57,7 @@ const captions = [ segmentLength: 3.84, }, /* ... */ -]; +] ``` The segment number is calculated from the presentation timeline. You MUST ensure your subtitle segments are enumerated to match your media segments and you account for offsets such as: @@ -73,12 +75,24 @@ You can style the subtitles by setting `media.subtitleCustomisation` in the `.in ```js // 1️⃣ Create an object mapping out styles for your subtitles. -playbackData.media.subtitleCustomisation = { lineHeight: 1.5, size: 1 }; +playbackData.media.subtitleCustomisation = { lineHeight: 1.5, size: 1 } // 2️⃣ Pass playback data that contains subtitle customisation (and captions) to the player. -player.init(document.querySelector("video"), playbackData, /* other opts */); +player.init(document.querySelector("video"), playbackData /* other opts */) ``` +### Low Latency Streams + +When using Dash.js with a low-latency MPD segments are delivered using Chunked Transfer Encoding (CTE) - the current side chain doesn't allow for delivery in this case. + +Whilst it is possible to collect chunks as they are delivered, wait until a full segment worth of subtitles have been delivered and pass these to the render function this breaks the low-latency workflow. + +An override has been added to allow subtitles to be rendered directly by Dash.js instead of the current side-chain. + +Subtitles can be enabled and disabled in the usual way using the `setSubtitlesEnabled()` function. However, they are signalled and delivered by the chosen MPD. + +Using Dash.js subtitles can be enabled using `window.bigscreenPlayer.overrides.dashSubtitles = true`. + ##  Design ### Why not include subtitles in the manifest? From 0d68c699565f67e6f817d20036b646bcee022796 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Mon, 23 Sep 2024 12:09:45 +0100 Subject: [PATCH 04/48] Subtitles: low-latency docs --- docs/tutorials/Subtitles.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/tutorials/Subtitles.md b/docs/tutorials/Subtitles.md index a97471589..50bdf181d 100644 --- a/docs/tutorials/Subtitles.md +++ b/docs/tutorials/Subtitles.md @@ -10,10 +10,12 @@ You provide subtitles to BigscreenPlayer by setting `media.captions` in the `.in ```js // 1️⃣ Add an array of caption blocks to your playback data. -playbackData.media.captions = [/* caption blocks... */]; +playbackData.media.captions = [ + /* caption blocks... */ +] // 2️⃣ Pass playback data that contains captions to the player. -player.init(document.querySelector("video"), playbackData, /* other opts */); +player.init(document.querySelector("video"), playbackData /* other opts */) ``` 1. `media.captions` MUST be an array containing at least one object. @@ -34,7 +36,7 @@ const captions = [ { url: "https://some.cdn/subtitles.xml" }, { url: "https://other.cdn/subtitles.xml" }, /* ... */ -]; +] ``` Subtitles delivered as a whole do not require any additional metadata in the manifest to work. @@ -46,7 +48,7 @@ Subtitles are delivered "as segments" when the captions' `url` is an URL templat ```js // Each segment specifies subtitles for a segment of the media experience. const captions = [ - { + { url: "https://some.cdn/subtitles/$segment$.m4s", segmentLength: 3.84, }, @@ -55,7 +57,7 @@ const captions = [ segmentLength: 3.84, }, /* ... */ -]; +] ``` The segment number is calculated from the presentation timeline. You MUST ensure your subtitle segments are enumerated to match your media segments and you account for offsets such as: @@ -73,12 +75,24 @@ You can style the subtitles by setting `media.subtitleCustomisation` in the `.in ```js // 1️⃣ Create an object mapping out styles for your subtitles. -playbackData.media.subtitleCustomisation = { lineHeight: 1.5, size: 1 }; +playbackData.media.subtitleCustomisation = { lineHeight: 1.5, size: 1 } // 2️⃣ Pass playback data that contains subtitle customisation (and captions) to the player. -player.init(document.querySelector("video"), playbackData, /* other opts */); +player.init(document.querySelector("video"), playbackData /* other opts */) ``` +### Low Latency Streams + +When using Dash.js with a low-latency MPD segments are delivered using Chunked Transfer Encoding (CTE) - the current side chain doesn't allow for delivery in this case. + +Whilst it is possible to collect chunks as they are delivered, wait until a full segment worth of subtitles have been delivered and pass these to the render function this breaks the low-latency workflow. + +An override has been added to allow subtitles to be rendered directly by Dash.js instead of the current side-chain. + +Subtitles can be enabled and disabled in the usual way using the `setSubtitlesEnabled()` function. However, they are signalled and delivered by the chosen MPD. + +Using Dash.js subtitles can be enabled using `window.bigscreenPlayer.overrides.dashSubtitles = true`. + ##  Design ### Why not include subtitles in the manifest? From 6279f29593be8a4fba47a2b3f39ee1c90f2c06dc Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Tue, 24 Sep 2024 11:57:58 +0100 Subject: [PATCH 05/48] Don't require a captions object when dashSubtitles override is used --- src/subtitles/subtitles.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/subtitles/subtitles.js b/src/subtitles/subtitles.js index 1580f4e56..d22a4e285 100644 --- a/src/subtitles/subtitles.js +++ b/src/subtitles/subtitles.js @@ -78,6 +78,10 @@ function Subtitles(mediaPlayer, autoStart, playbackElement, defaultStyleOpts, me } function available() { + if (dashSubs) { + return true + } + const url = mediaSources.currentSubtitlesSource() if (!(typeof url === "string" && url !== "")) { @@ -86,7 +90,7 @@ function Subtitles(mediaPlayer, autoStart, playbackElement, defaultStyleOpts, me const isWhole = findSegmentTemplate(url) == null - return isWhole || (!useLegacySubs && !dashSubs && isSeekableLiveSupport) + return isWhole || (!useLegacySubs && isSeekableLiveSupport) } function setPosition(position) { From db5e2d6e3d80d66d73d9069baa371ef4f8d50b78 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Fri, 27 Sep 2024 14:29:08 +0100 Subject: [PATCH 06/48] Add tests for DashSubtitles --- src/subtitles/dashsubtitles.js | 10 ++-- src/subtitles/dashsubtitles.test.js | 84 +++++++++++++++++++++++++++++ src/subtitles/subtitles.test.js | 4 +- 3 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 src/subtitles/dashsubtitles.test.js diff --git a/src/subtitles/dashsubtitles.js b/src/subtitles/dashsubtitles.js index 7847e3527..a1fae6a29 100644 --- a/src/subtitles/dashsubtitles.js +++ b/src/subtitles/dashsubtitles.js @@ -1,3 +1,5 @@ +import DOMHelpers from "../domhelpers" + function DashSubtitles(mediaPlayer, autoStart, parentElement) { let currentSubtitlesElement @@ -31,15 +33,17 @@ function DashSubtitles(mediaPlayer, autoStart, parentElement) { mediaPlayer.setSubtitles(false) } + function tearDown() { + stop() + } + addCurrentSubtitlesElement() return { start, stop, customise: () => {}, - tearDown: () => { - stop() - }, + tearDown, } } diff --git a/src/subtitles/dashsubtitles.test.js b/src/subtitles/dashsubtitles.test.js new file mode 100644 index 000000000..0677711b9 --- /dev/null +++ b/src/subtitles/dashsubtitles.test.js @@ -0,0 +1,84 @@ +import DashSubtitles from "./dashsubtitles" + +const UPDATE_INTERVAL = 750 + +describe("Dash Subtitles", () => { + let subtitles + let targetElement + + const mockMediaPlayer = { + getCurrentTime: jest.fn(), + setSubtitles: jest.fn(), + } + + beforeEach(() => { + jest.useFakeTimers() + jest.clearAllMocks() + jest.clearAllTimers() + + // Reset the target HTML element between each test + targetElement?.remove() + targetElement = document.createElement("div") + + jest.spyOn(targetElement, "clientWidth", "get").mockReturnValue(200) + jest.spyOn(targetElement, "clientHeight", "get").mockReturnValue(100) + jest.spyOn(targetElement, "removeChild") + + document.body.appendChild(targetElement) + + // Reset instance + subtitles?.tearDown() + subtitles = null + mockMediaPlayer.setSubtitles.mockClear() + }) + + function progressTime(mediaPlayerTime) { + mockMediaPlayer.getCurrentTime.mockReturnValue(mediaPlayerTime) + jest.advanceTimersByTime(UPDATE_INTERVAL) + } + + describe("construction", () => { + it("returns the correct interface", () => { + const autoStart = false + + subtitles = DashSubtitles(mockMediaPlayer, autoStart, targetElement) + + expect(subtitles).toEqual( + expect.objectContaining({ + start: expect.any(Function), + stop: expect.any(Function), + customise: expect.any(Function), + tearDown: expect.any(Function), + }) + ) + }) + + it("Expect TTML rendering div to have been created", () => { + const autoStart = false + subtitles = DashSubtitles(mockMediaPlayer, autoStart, targetElement) + + progressTime(1.5) + expect(targetElement.querySelector("#bsp_subtitles")).toBeTruthy() + }) + }) + + describe("autoplay", () => { + it("triggers the MSE player to enable subtitles immediately when set to autoplay", () => { + const autoStart = true + + subtitles = DashSubtitles(mockMediaPlayer, autoStart, targetElement) + + progressTime(1.5) + expect(mockMediaPlayer.setSubtitles).toHaveBeenCalledTimes(1) + }) + + it("does not trigger the MSE player to enable subtitles immediately when set to autoplay", () => { + const autoStart = false + + subtitles = DashSubtitles(mockMediaPlayer, autoStart, targetElement) + + progressTime(1.5) + expect(mockMediaPlayer.setSubtitles).toHaveBeenCalledTimes(0) + }) + }) +}) diff --git a/src/subtitles/subtitles.test.js b/src/subtitles/subtitles.test.js index c6d140b5d..467e7dd19 100644 --- a/src/subtitles/subtitles.test.js +++ b/src/subtitles/subtitles.test.js @@ -99,7 +99,7 @@ describe("Subtitles", () => { }) }) - it("implementation is not available when dash subtitles override is true, but subtitles are segmented", (done) => { + it("implementation is available when dash subtitles override is true, even if segmented URL is passed", (done) => { isSegmented = true const mockMediaPlayer = {} const autoStart = true @@ -107,7 +107,7 @@ describe("Subtitles", () => { Subtitles(mockMediaPlayer, autoStart, playbackElement, null, mockMediaSources, () => { expect(LegacySubtitles).not.toHaveBeenCalled() expect(IMSCSubtitles).not.toHaveBeenCalled() - expect(DashSubtitles).not.toHaveBeenCalled() + expect(DashSubtitles).toHaveBeenCalledTimes(1) done() }) }) From 3f656961c6979bdf7494f965d2ba65a8b38ddce8 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Thu, 10 Oct 2024 13:34:38 +0100 Subject: [PATCH 07/48] Rename DashSubs to EmbeddedSubs --- docs/tutorials/Subtitles.md | 6 ++++-- src/playbackstrategy/msestrategy.js | 4 ++-- .../{dashsubtitles.js => embeddedsubtitles.js} | 4 ++-- ...itles.test.js => embeddedsubtitles.test.js} | 12 ++++++------ src/subtitles/subtitles.js | 18 ++++++++++++------ src/subtitles/subtitles.test.js | 18 +++++++++--------- 6 files changed, 35 insertions(+), 27 deletions(-) rename src/subtitles/{dashsubtitles.js => embeddedsubtitles.js} (90%) rename src/subtitles/{dashsubtitles.test.js => embeddedsubtitles.test.js} (83%) diff --git a/docs/tutorials/Subtitles.md b/docs/tutorials/Subtitles.md index 50bdf181d..794994138 100644 --- a/docs/tutorials/Subtitles.md +++ b/docs/tutorials/Subtitles.md @@ -51,10 +51,12 @@ const captions = [ { url: "https://some.cdn/subtitles/$segment$.m4s", segmentLength: 3.84, + cdn: "default", }, { url: "https://other.cdn/subtitles/$segment$.m4s", segmentLength: 3.84, + cdn: "default", }, /* ... */ ] @@ -83,7 +85,7 @@ player.init(document.querySelector("video"), playbackData /* other opts */) ### Low Latency Streams -When using Dash.js with a low-latency MPD segments are delivered using Chunked Transfer Encoding (CTE) - the current side chain doesn't allow for delivery in this case. +When using Dash.js with a low-latency MPD segments are delivered using Chunked Transfer Encoding (CTE) - the default side chain doesn't allow for delivery in this case. Whilst it is possible to collect chunks as they are delivered, wait until a full segment worth of subtitles have been delivered and pass these to the render function this breaks the low-latency workflow. @@ -91,7 +93,7 @@ An override has been added to allow subtitles to be rendered directly by Dash.js Subtitles can be enabled and disabled in the usual way using the `setSubtitlesEnabled()` function. However, they are signalled and delivered by the chosen MPD. -Using Dash.js subtitles can be enabled using `window.bigscreenPlayer.overrides.dashSubtitles = true`. +Using Dash.js subtitles can be enabled using `window.bigscreenPlayer.overrides.embeddedSubtitles = true`. ##  Design diff --git a/src/playbackstrategy/msestrategy.js b/src/playbackstrategy/msestrategy.js index 7f5601d85..d0633ba9a 100644 --- a/src/playbackstrategy/msestrategy.js +++ b/src/playbackstrategy/msestrategy.js @@ -500,13 +500,13 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD function setUpMediaPlayer(playbackTime) { const dashSettings = getDashSettings(playerSettings) - const dashSubs = window.bigscreenPlayer?.overrides?.dashSubtitles ?? false + const embeddedSubs = window.bigscreenPlayer?.overrides?.embeddedSubtitles ?? false mediaPlayer = MediaPlayer().create() mediaPlayer.updateSettings(dashSettings) mediaPlayer.initialize(mediaElement, null, true) - if (dashSubs) { + if (embeddedSubs) { mediaPlayer.attachTTMLRenderingDiv(document.querySelector("#bsp_subtitles")) } diff --git a/src/subtitles/dashsubtitles.js b/src/subtitles/embeddedsubtitles.js similarity index 90% rename from src/subtitles/dashsubtitles.js rename to src/subtitles/embeddedsubtitles.js index a1fae6a29..7dd4772f0 100644 --- a/src/subtitles/dashsubtitles.js +++ b/src/subtitles/embeddedsubtitles.js @@ -1,6 +1,6 @@ import DOMHelpers from "../domhelpers" -function DashSubtitles(mediaPlayer, autoStart, parentElement) { +function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement) { let currentSubtitlesElement if (autoStart) { @@ -47,4 +47,4 @@ function DashSubtitles(mediaPlayer, autoStart, parentElement) { } } -export default DashSubtitles +export default EmbeddedSubtitles diff --git a/src/subtitles/dashsubtitles.test.js b/src/subtitles/embeddedsubtitles.test.js similarity index 83% rename from src/subtitles/dashsubtitles.test.js rename to src/subtitles/embeddedsubtitles.test.js index 0677711b9..d5051c629 100644 --- a/src/subtitles/dashsubtitles.test.js +++ b/src/subtitles/embeddedsubtitles.test.js @@ -1,8 +1,8 @@ -import DashSubtitles from "./dashsubtitles" +import EmbeddedSubtitles from "./embeddedsubtitles" const UPDATE_INTERVAL = 750 -describe("Dash Subtitles", () => { +describe("Embedded Subtitles", () => { let subtitles let targetElement @@ -41,7 +41,7 @@ describe("Dash Subtitles", () => { it("returns the correct interface", () => { const autoStart = false - subtitles = DashSubtitles(mockMediaPlayer, autoStart, targetElement) + subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement) expect(subtitles).toEqual( expect.objectContaining({ @@ -55,7 +55,7 @@ describe("Dash Subtitles", () => { it("Expect TTML rendering div to have been created", () => { const autoStart = false - subtitles = DashSubtitles(mockMediaPlayer, autoStart, targetElement) + subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement) progressTime(1.5) expect(targetElement.querySelector("#bsp_subtitles")).toBeTruthy() @@ -66,7 +66,7 @@ describe("Dash Subtitles", () => { it("triggers the MSE player to enable subtitles immediately when set to autoplay", () => { const autoStart = true - subtitles = DashSubtitles(mockMediaPlayer, autoStart, targetElement) + subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement) progressTime(1.5) expect(mockMediaPlayer.setSubtitles).toHaveBeenCalledTimes(1) @@ -75,7 +75,7 @@ describe("Dash Subtitles", () => { it("does not trigger the MSE player to enable subtitles immediately when set to autoplay", () => { const autoStart = false - subtitles = DashSubtitles(mockMediaPlayer, autoStart, targetElement) + subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement) progressTime(1.5) expect(mockMediaPlayer.setSubtitles).toHaveBeenCalledTimes(0) diff --git a/src/subtitles/subtitles.js b/src/subtitles/subtitles.js index d22a4e285..b1a102f9d 100644 --- a/src/subtitles/subtitles.js +++ b/src/subtitles/subtitles.js @@ -3,7 +3,7 @@ import findSegmentTemplate from "../utils/findtemplate" function Subtitles(mediaPlayer, autoStart, playbackElement, defaultStyleOpts, mediaSources, callback) { const useLegacySubs = window.bigscreenPlayer?.overrides?.legacySubtitles ?? false - const dashSubs = window.bigscreenPlayer?.overrides?.dashSubtitles ?? false + const embeddedSubs = window.bigscreenPlayer?.overrides?.embeddedSubtitles ?? false const isSeekableLiveSupport = window.bigscreenPlayer.liveSupport == null || window.bigscreenPlayer.liveSupport === "seekable" @@ -21,10 +21,16 @@ function Subtitles(mediaPlayer, autoStart, playbackElement, defaultStyleOpts, me .catch(() => { Plugins.interface.onSubtitlesDynamicLoadError() }) - } else if (dashSubs) { - import("./dashsubtitles.js") - .then(({ default: DashSubtitles }) => { - subtitlesContainer = DashSubtitles(mediaPlayer, autoStart, playbackElement, mediaSources, defaultStyleOpts) + } else if (embeddedSubs) { + import("./embeddedsubtitles.js") + .then(({ default: EmbeddedSubtitles }) => { + subtitlesContainer = EmbeddedSubtitles( + mediaPlayer, + autoStart, + playbackElement, + mediaSources, + defaultStyleOpts + ) callback(subtitlesEnabled) }) .catch(() => { @@ -78,7 +84,7 @@ function Subtitles(mediaPlayer, autoStart, playbackElement, defaultStyleOpts, me } function available() { - if (dashSubs) { + if (embeddedSubs) { return true } diff --git a/src/subtitles/subtitles.test.js b/src/subtitles/subtitles.test.js index 467e7dd19..39dac1b6b 100644 --- a/src/subtitles/subtitles.test.js +++ b/src/subtitles/subtitles.test.js @@ -1,13 +1,13 @@ /* eslint-disable jest/no-done-callback */ import IMSCSubtitles from "./imscsubtitles" import LegacySubtitles from "./legacysubtitles" -import DashSubtitles from "./dashsubtitles" +import EmbeddedSubtitles from "./embeddedsubtitles" import Subtitles from "./subtitles" jest.mock("./imscsubtitles") jest.mock("./legacysubtitles") -jest.mock("./dashsubtitles") +jest.mock("./embeddedsubtitles") describe("Subtitles", () => { let isAvailable @@ -77,29 +77,29 @@ describe("Subtitles", () => { }) }) - describe("dash", () => { + describe("embedded", () => { beforeEach(() => { window.bigscreenPlayer = { overrides: { - dashSubtitles: true, + embeddedSubtitles: true, }, } - DashSubtitles.mockReset() + EmbeddedSubtitles.mockReset() }) - it("implementation is available when dash subtitles override is true", (done) => { + it("implementation is available when embedded subtitles override is true", (done) => { const mockMediaPlayer = {} const autoStart = true Subtitles(mockMediaPlayer, autoStart, playbackElement, null, mockMediaSources, (result) => { expect(result).toBe(true) - expect(DashSubtitles).toHaveBeenCalledTimes(1) + expect(EmbeddedSubtitles).toHaveBeenCalledTimes(1) done() }) }) - it("implementation is available when dash subtitles override is true, even if segmented URL is passed", (done) => { + it("implementation is available when embedded subtitles override is true, even if segmented URL is passed", (done) => { isSegmented = true const mockMediaPlayer = {} const autoStart = true @@ -107,7 +107,7 @@ describe("Subtitles", () => { Subtitles(mockMediaPlayer, autoStart, playbackElement, null, mockMediaSources, () => { expect(LegacySubtitles).not.toHaveBeenCalled() expect(IMSCSubtitles).not.toHaveBeenCalled() - expect(DashSubtitles).toHaveBeenCalledTimes(1) + expect(EmbeddedSubtitles).toHaveBeenCalledTimes(1) done() }) }) From 4c6edf7b71cd89fc43ca6e1642905c990ba3a398 Mon Sep 17 00:00:00 2001 From: Tom Coward <34864926+tom-coward@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:10:27 +0100 Subject: [PATCH 08/48] Fix merge (missing }) --- src/playbackstrategy/msestrategy.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/playbackstrategy/msestrategy.js b/src/playbackstrategy/msestrategy.js index 687ae6cbc..159e98e11 100644 --- a/src/playbackstrategy/msestrategy.js +++ b/src/playbackstrategy/msestrategy.js @@ -583,6 +583,7 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD const textTracks = mediaElement.textTracks for (let index = 0; index < textTracks.length; index++) { textTracks[index].mode = "showing" + } } function manifestLoadingFinished(event) { From 485ca684a149d6ad0b5f28c47228acedd7db632d Mon Sep 17 00:00:00 2001 From: Tom Coward <34864926+tom-coward@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:47:47 +0000 Subject: [PATCH 09/48] Wait for media player to be ready before enabling embedded subtitles --- src/subtitles/embeddedsubtitles.js | 5 +++++ src/subtitles/embeddedsubtitles.test.js | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/subtitles/embeddedsubtitles.js b/src/subtitles/embeddedsubtitles.js index 7dd4772f0..bc0238bee 100644 --- a/src/subtitles/embeddedsubtitles.js +++ b/src/subtitles/embeddedsubtitles.js @@ -4,7 +4,12 @@ function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement) { let currentSubtitlesElement if (autoStart) { + mediaPlayer.addEventCallback(this, onMediaPlayerReady) + } + + function onMediaPlayerReady() { start() + mediaPlayer.removeEventCallback(this, onMediaPlayerReady) } function removeCurrentSubtitlesElement() { diff --git a/src/subtitles/embeddedsubtitles.test.js b/src/subtitles/embeddedsubtitles.test.js index d5051c629..a66910f3a 100644 --- a/src/subtitles/embeddedsubtitles.test.js +++ b/src/subtitles/embeddedsubtitles.test.js @@ -9,6 +9,7 @@ describe("Embedded Subtitles", () => { const mockMediaPlayer = { getCurrentTime: jest.fn(), setSubtitles: jest.fn(), + addEventCallback: jest.fn(), } beforeEach(() => { @@ -63,7 +64,7 @@ describe("Embedded Subtitles", () => { }) describe("autoplay", () => { - it("triggers the MSE player to enable subtitles immediately when set to autoplay", () => { + it.skip("triggers the MSE player to enable subtitles immediately when set to autoplay", () => { const autoStart = true subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement) From 591a5537b3c8749b21dce0572e5091cccb2303db Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Thu, 6 Feb 2025 18:04:46 +0000 Subject: [PATCH 10/48] Pass IMSCjs style options through BSP to embeddedsubtitles --- src/playbackstrategy/msestrategy.js | 5 +++++ src/playercomponent.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/playbackstrategy/msestrategy.js b/src/playbackstrategy/msestrategy.js index 159e98e11..303096429 100644 --- a/src/playbackstrategy/msestrategy.js +++ b/src/playbackstrategy/msestrategy.js @@ -608,6 +608,10 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD } } + function customiseSubtitles(options) { + return mediaPlayer && mediaPlayer.updateSettings({ streaming: { text: { imsc: { options } } } }) + } + function getDuration() { return mediaPlayer && mediaPlayer.isReady() ? mediaPlayer.duration() : 0 } @@ -777,6 +781,7 @@ function MSEStrategy(mediaSources, windowType, mediaKind, playbackElement, isUHD startAutoResumeTimeout() } }, + customiseSubtitles, play: () => mediaPlayer.play(), setCurrentTime: (time) => { publishedSeekEvent = false diff --git a/src/playercomponent.js b/src/playercomponent.js index b7657c9be..a32c8854b 100644 --- a/src/playercomponent.js +++ b/src/playercomponent.js @@ -75,6 +75,10 @@ function PlayerComponent( return playbackStrategy && playbackStrategy.setSubtitles(state) } + function customiseSubtitles(styleOpts) { + return playbackStrategy && playbackStrategy.customiseSubtitles(styleOpts) + } + function getDuration() { return playbackStrategy && playbackStrategy.getDuration() } @@ -399,6 +403,7 @@ function PlayerComponent( play, pause, setSubtitles, + customiseSubtitles, transitions, isEnded, setPlaybackRate, From 56c838689f80dd8c7354f545b38ecc21a0f1b897 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Sun, 9 Feb 2025 16:52:09 +0000 Subject: [PATCH 11/48] Add customisation to embedded subtitles --- src/subtitles/embeddedsubtitles.js | 41 ++++++++++++++-- src/subtitles/embeddedsubtitles.test.js | 62 +++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 8 deletions(-) diff --git a/src/subtitles/embeddedsubtitles.js b/src/subtitles/embeddedsubtitles.js index bc0238bee..2171f8bd4 100644 --- a/src/subtitles/embeddedsubtitles.js +++ b/src/subtitles/embeddedsubtitles.js @@ -1,14 +1,17 @@ import DOMHelpers from "../domhelpers" +import Utils from "../utils/playbackutils" -function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement) { +function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, defaultStyleOpts) { let currentSubtitlesElement + let imscRenderOpts = transformStyleOptions(defaultStyleOpts) + if (autoStart) { + start() mediaPlayer.addEventCallback(this, onMediaPlayerReady) } function onMediaPlayerReady() { - start() mediaPlayer.removeEventCallback(this, onMediaPlayerReady) } @@ -29,6 +32,7 @@ function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement) { function start() { mediaPlayer.setSubtitles(true) + customise(imscRenderOpts) if (!currentSubtitlesElement) { addCurrentSubtitlesElement() } @@ -42,12 +46,43 @@ function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement) { stop() } + function customise(styleOpts) { + const customStyleOptions = transformStyleOptions(styleOpts) + imscRenderOpts = Utils.merge(imscRenderOpts, customStyleOptions) + mediaPlayer.customiseSubtitles(imscRenderOpts) + } + + // Opts: { backgroundColour: string (css colour, hex), fontFamily: string , size: number, lineHeight: number } + function transformStyleOptions(opts) { + if (opts === undefined) return + + const customStyles = {} + + if (opts.backgroundColour) { + customStyles.spanBackgroundColorAdjust = { transparent: opts.backgroundColour } + } + + if (opts.fontFamily) { + customStyles.fontFamily = opts.fontFamily + } + + if (opts.size > 0) { + customStyles.sizeAdjust = opts.size + } + + if (opts.lineHeight) { + customStyles.lineHeightAdjust = opts.lineHeight + } + + return customStyles + } + addCurrentSubtitlesElement() return { start, stop, - customise: () => {}, + customise, tearDown, } } diff --git a/src/subtitles/embeddedsubtitles.test.js b/src/subtitles/embeddedsubtitles.test.js index a66910f3a..e7d315bc8 100644 --- a/src/subtitles/embeddedsubtitles.test.js +++ b/src/subtitles/embeddedsubtitles.test.js @@ -10,6 +10,7 @@ describe("Embedded Subtitles", () => { getCurrentTime: jest.fn(), setSubtitles: jest.fn(), addEventCallback: jest.fn(), + customiseSubtitles: jest.fn(), } beforeEach(() => { @@ -42,7 +43,7 @@ describe("Embedded Subtitles", () => { it("returns the correct interface", () => { const autoStart = false - subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement) + subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement, null, {}) expect(subtitles).toEqual( expect.objectContaining({ @@ -56,7 +57,7 @@ describe("Embedded Subtitles", () => { it("Expect TTML rendering div to have been created", () => { const autoStart = false - subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement) + subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement, null, {}) progressTime(1.5) expect(targetElement.querySelector("#bsp_subtitles")).toBeTruthy() @@ -64,10 +65,10 @@ describe("Embedded Subtitles", () => { }) describe("autoplay", () => { - it.skip("triggers the MSE player to enable subtitles immediately when set to autoplay", () => { + it("triggers the MSE player to enable subtitles immediately when set to autoplay", () => { const autoStart = true - subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement) + subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement, null, {}) progressTime(1.5) expect(mockMediaPlayer.setSubtitles).toHaveBeenCalledTimes(1) @@ -76,10 +77,61 @@ describe("Embedded Subtitles", () => { it("does not trigger the MSE player to enable subtitles immediately when set to autoplay", () => { const autoStart = false - subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement) + subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement, null, {}) progressTime(1.5) expect(mockMediaPlayer.setSubtitles).toHaveBeenCalledTimes(0) }) }) + + describe("customisation", () => { + it("overrides the subtitles styling metadata with supplied defaults when rendering", () => { + const expectedStyles = { spanBackgroundColorAdjust: { transparent: "black" }, fontFamily: "Arial" } + + subtitles = EmbeddedSubtitles(mockMediaPlayer, false, targetElement, null, { + backgroundColour: "black", + fontFamily: "Arial", + }) + + subtitles.start() + + progressTime(1) + + expect(mockMediaPlayer.customiseSubtitles).toHaveBeenCalledWith(expectedStyles) + }) + + it("overrides the subtitles styling metadata with supplied custom styles when rendering", () => { + subtitles = EmbeddedSubtitles(mockMediaPlayer, false, targetElement, null, {}) + + const styleOpts = { size: 0.7, lineHeight: 0.9 } + const expectedOpts = { sizeAdjust: 0.7, lineHeightAdjust: 0.9 } + + mockMediaPlayer.getCurrentTime.mockReturnValueOnce(1) + + subtitles.start() + subtitles.customise(styleOpts) + + expect(mockMediaPlayer.customiseSubtitles).toHaveBeenCalledWith(expectedOpts) + }) + + it("merges the current subtitles styling metadata with new supplied custom styles when rendering", () => { + const defaultStyleOpts = { backgroundColour: "black", fontFamily: "Arial" } + const customStyleOpts = { size: 0.7, lineHeight: 0.9 } + const expectedOpts = { + spanBackgroundColorAdjust: { transparent: "black" }, + fontFamily: "Arial", + sizeAdjust: 0.7, + lineHeightAdjust: 0.9, + } + + subtitles = EmbeddedSubtitles(mockMediaPlayer, false, targetElement, null, defaultStyleOpts) + + mockMediaPlayer.getCurrentTime.mockReturnValueOnce(1) + + subtitles.start() + subtitles.customise(customStyleOpts) + + expect(mockMediaPlayer.customiseSubtitles).toHaveBeenCalledWith(expectedOpts) + }) + }) }) From 6a605b812b3f82634181de8612ca7db5c0c6070f Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Mon, 28 Apr 2025 12:13:39 +0100 Subject: [PATCH 12/48] Alias smp-imsc in dev and build --- package-lock.json | 35 +++++++++++------------------------ package.json | 4 ++-- rollup.config.js | 4 ++++ rollup.dev.config.js | 4 ++++ 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 61cd1baf3..b05a06db8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "dashjs": "github:bbc/dash.js#smp-v4.7.3-6", + "dashjs": "github:bbc/rd-dash.js#rd-imsc-customisation", "smp-imsc": "github:bbc/imscJS#v1.0.3" }, "devDependencies": { @@ -18,7 +18,7 @@ "@babel/plugin-transform-runtime": "^7.23.9", "@babel/preset-env": "^7.23.8", "@babel/preset-typescript": "^7.23.3", - "@rollup/plugin-alias": "^5.1.0", + "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-inject": "^5.0.5", @@ -2848,13 +2848,11 @@ } }, "node_modules/@rollup/plugin-alias": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.0.tgz", - "integrity": "sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz", + "integrity": "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==", "dev": true, - "dependencies": { - "slash": "^4.0.0" - }, + "license": "MIT", "engines": { "node": ">=14.0.0" }, @@ -2867,18 +2865,6 @@ } } }, - "node_modules/@rollup/plugin-alias/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@rollup/plugin-babel": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz", @@ -5620,7 +5606,7 @@ }, "node_modules/dashjs": { "version": "4.7.3", - "resolved": "git+ssh://git@github.com/bbc/dash.js.git#84ad9d4d3abc3212dd6951bf44961905fc82f672", + "resolved": "git+ssh://git@github.com/bbc/rd-dash.js.git#bb0646d17a0c219ef7d236ffaf21386d50d241f2", "license": "BSD-3-Clause", "dependencies": { "bcp-47-match": "^2.0.3", @@ -8248,9 +8234,10 @@ } }, "node_modules/imsc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/imsc/-/imsc-1.1.4.tgz", - "integrity": "sha512-s/WbXG6IbeW6X/8sBJWcQD22mwRcnpI55b8Kr3sbcONUaeMLkpHle/PE1xcMN9HJrMc5idrCwNV7wtZ8EBsFnw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/imsc/-/imsc-1.1.5.tgz", + "integrity": "sha512-V8je+CGkcvGhgl2C1GlhqFFiUOIEdwXbXLiu1Fcubvvbo+g9inauqT3l0pNYXGoLPBj3jxtZz9t+wCopMkwadQ==", + "license": "BSD-2-Clause", "dependencies": { "sax": "1.2.1" } diff --git a/package.json b/package.json index 31ae852f9..04528412e 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@babel/plugin-transform-runtime": "^7.23.9", "@babel/preset-env": "^7.23.8", "@babel/preset-typescript": "^7.23.3", - "@rollup/plugin-alias": "^5.1.0", + "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-inject": "^5.0.5", @@ -64,7 +64,7 @@ "typescript-eslint": "^7.2.0" }, "dependencies": { - "dashjs": "github:bbc/dash.js#smp-v4.7.3-6", + "dashjs": "github:bbc/rd-dash.js#rd-imsc-customisation", "smp-imsc": "github:bbc/imscJS#v1.0.3" }, "repository": { diff --git a/rollup.config.js b/rollup.config.js index dfee102cd..46ba60796 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,5 +1,6 @@ import PackageJSON from "./package.json" assert { type: "json" } +import alias from "@rollup/plugin-alias" import replace from "@rollup/plugin-replace" import typescript from "@rollup/plugin-typescript" import { dts } from "rollup-plugin-dts" @@ -10,6 +11,9 @@ export default [ external: [/^dashjs/, "smp-imsc", "tslib"], output: [{ dir: "dist/esm", format: "es" }], plugins: [ + alias({ + entries: [{ find: "imsc", replacement: "smp-imsc" }], + }), replace({ preventAssignment: true, __VERSION__: () => PackageJSON.version, diff --git a/rollup.dev.config.js b/rollup.dev.config.js index a541eac0c..866a0fe6b 100644 --- a/rollup.dev.config.js +++ b/rollup.dev.config.js @@ -1,5 +1,6 @@ import PackageJSON from "./package.json" assert { type: "json" } +import alias from "@rollup/plugin-alias" import babel from "@rollup/plugin-babel" import commonjs from "@rollup/plugin-commonjs" import resolve from "@rollup/plugin-node-resolve" @@ -20,6 +21,9 @@ export default { format: "es", }, plugins: [ + alias({ + entries: [{ find: "imsc", replacement: "smp-imsc" }], + }), replace({ preventAssignment: true, __VERSION__: () => PackageJSON.version, From 4e08ddc267466d8a5d462fe77667365c7bc7af30 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Mon, 28 Apr 2025 12:16:08 +0100 Subject: [PATCH 13/48] Add TTML Rendering Div if embedded --- src/playbackstrategy/msestrategy.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/playbackstrategy/msestrategy.js b/src/playbackstrategy/msestrategy.js index a993cc84c..3e2080348 100644 --- a/src/playbackstrategy/msestrategy.js +++ b/src/playbackstrategy/msestrategy.js @@ -594,7 +594,9 @@ function MSEStrategy( role: "main", } ) - + + embeddedSubs && mediaPlayer.attachTTMLRenderingDiv(document.querySelector("#bsp_subtitles")) + modifySource(presentationTimeInSeconds) } From 04aceba7056937aa2088480a5571fe15801702f6 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Mon, 28 Apr 2025 17:11:41 +0100 Subject: [PATCH 14/48] Add example subtitle using smp-imsc to embedded subs --- src/subtitles/embeddedsubtitles.js | 54 ++++++++++++++ src/subtitles/embeddedsubtitles.test.js | 97 +++++++++++++++++++++++++ 2 files changed, 151 insertions(+) diff --git a/src/subtitles/embeddedsubtitles.js b/src/subtitles/embeddedsubtitles.js index 2171f8bd4..eec148331 100644 --- a/src/subtitles/embeddedsubtitles.js +++ b/src/subtitles/embeddedsubtitles.js @@ -1,8 +1,10 @@ +import { fromXML, generateISD, renderHTML } from "smp-imsc" import DOMHelpers from "../domhelpers" import Utils from "../utils/playbackutils" function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, defaultStyleOpts) { let currentSubtitlesElement + let exampleSubtitlesElement let imscRenderOpts = transformStyleOptions(defaultStyleOpts) @@ -22,6 +24,55 @@ function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, } } + function removeExampleSubtitlesElement() { + if (exampleSubtitlesElement) { + DOMHelpers.safeRemoveElement(exampleSubtitlesElement) + exampleSubtitlesElement = undefined + } + } + + function renderExample(exampleXmlString, styleOpts, safePosition = {}) { + const exampleXml = fromXML(exampleXmlString) + removeExampleSubtitlesElement() + + const customStyleOptions = transformStyleOptions(styleOpts) + const exampleStyle = Utils.merge(imscRenderOpts, customStyleOptions) + + exampleSubtitlesElement = document.createElement("div") + exampleSubtitlesElement.id = "subtitlesPreview" + exampleSubtitlesElement.style.position = "absolute" + + const elementWidth = parentElement.clientWidth + const elementHeight = parentElement.clientHeight + const topPixels = ((safePosition.top || 0) / 100) * elementHeight + const rightPixels = ((safePosition.right || 0) / 100) * elementWidth + const bottomPixels = ((safePosition.bottom || 0) / 100) * elementHeight + const leftPixels = ((safePosition.left || 0) / 100) * elementWidth + + const renderWidth = elementWidth - leftPixels - rightPixels + const renderHeight = elementHeight - topPixels - bottomPixels + + exampleSubtitlesElement.style.top = `${topPixels}px` + exampleSubtitlesElement.style.right = `${rightPixels}px` + exampleSubtitlesElement.style.bottom = `${bottomPixels}px` + exampleSubtitlesElement.style.left = `${leftPixels}px` + parentElement.appendChild(exampleSubtitlesElement) + + renderSubtitle(exampleXml, 1, exampleSubtitlesElement, exampleStyle, renderHeight, renderWidth) + } + + function renderSubtitle(xml, currentTime, subsElement, styleOpts, renderHeight, renderWidth) { + try { + const isd = generateISD(xml, currentTime) + renderHTML(isd, subsElement, null, renderHeight, renderWidth, false, null, null, false, styleOpts) + } catch (error) { + error.name = "SubtitlesRenderError" + DebugTool.error(error) + + Plugins.interface.onSubtitlesRenderError() + } + } + function addCurrentSubtitlesElement() { removeCurrentSubtitlesElement() currentSubtitlesElement = document.createElement("div") @@ -82,7 +133,10 @@ function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, return { start, stop, + updatePosition: () => {}, customise, + renderExample, + clearExample: removeExampleSubtitlesElement, tearDown, } } diff --git a/src/subtitles/embeddedsubtitles.test.js b/src/subtitles/embeddedsubtitles.test.js index e7d315bc8..78e5876df 100644 --- a/src/subtitles/embeddedsubtitles.test.js +++ b/src/subtitles/embeddedsubtitles.test.js @@ -1,7 +1,20 @@ import EmbeddedSubtitles from "./embeddedsubtitles" +import { fromXML, generateISD, renderHTML } from "smp-imsc" + +jest.mock("smp-imsc") const UPDATE_INTERVAL = 750 +const mockImscDoc = { + getMediaTimeEvents: () => [1, 3, 8], + head: { + styling: {}, + }, + body: { + contents: [], + }, +} + describe("Embedded Subtitles", () => { let subtitles let targetElement @@ -13,11 +26,18 @@ describe("Embedded Subtitles", () => { customiseSubtitles: jest.fn(), } + beforeAll(() => { + fromXML.mockReturnValue(mockImscDoc) + generateISD.mockReturnValue({ contents: ["mockContents"] }) + }) + beforeEach(() => { jest.useFakeTimers() jest.clearAllMocks() jest.clearAllTimers() + mockMediaPlayer.getCurrentTime.mockReturnValue(0) + // Reset the target HTML element between each test targetElement?.remove() targetElement = document.createElement("div") @@ -28,8 +48,12 @@ describe("Embedded Subtitles", () => { document.body.appendChild(targetElement) + // Reset instance + subtitles?.stop() + // Reset instance subtitles?.tearDown() + subtitles = null mockMediaPlayer.setSubtitles.mockClear() }) @@ -134,4 +158,77 @@ describe("Embedded Subtitles", () => { expect(mockMediaPlayer.customiseSubtitles).toHaveBeenCalledWith(expectedOpts) }) }) + + describe("example rendering", () => { + it("should call fromXML, generate and render when renderExample is called", () => { + subtitles = EmbeddedSubtitles(mockMediaPlayer, false, targetElement, null, {}) + + subtitles.renderExample("", {}, {}) + + expect(fromXML).toHaveBeenCalledTimes(1) + expect(generateISD).toHaveBeenCalledTimes(1) + expect(renderHTML).toHaveBeenCalledTimes(1) + }) + + it("should call renderHTML with a preview element with the correct structure when no position info", () => { + subtitles = EmbeddedSubtitles(mockMediaPlayer, false, targetElement, null, {}) + + let exampleSubsElement = null + let height = null + let width = null + + renderHTML.mockImplementation((isd, subsElement, _, renderHeight, renderWidth) => { + exampleSubsElement = subsElement + height = renderHeight + width = renderWidth + }) + + subtitles.renderExample("", {}, {}) + + expect(renderHTML).toHaveBeenCalledTimes(1) + + expect(exampleSubsElement.style.top).toBe("0px") + expect(exampleSubsElement.style.right).toBe("0px") + expect(exampleSubsElement.style.bottom).toBe("0px") + expect(exampleSubsElement.style.left).toBe("0px") + + expect(height).toBe(100) + expect(width).toBe(200) + }) + + it("should call renderHTML with a preview element with the correct structure when there is position info", () => { + subtitles = EmbeddedSubtitles(mockMediaPlayer, false, targetElement, null, {}) + + let exampleSubsElement = null + let height = null + let width = null + + renderHTML.mockImplementation((isd, subsElement, _, renderHeight, renderWidth) => { + exampleSubsElement = subsElement + height = renderHeight + width = renderWidth + }) + + subtitles.renderExample( + "", + {}, + { + top: 1, + right: 2, + bottom: 3, + left: 4, + } + ) + + expect(renderHTML).toHaveBeenCalledTimes(1) + + expect(exampleSubsElement.style.top).toBe("1px") + expect(exampleSubsElement.style.right).toBe("4px") + expect(exampleSubsElement.style.bottom).toBe("3px") + expect(exampleSubsElement.style.left).toBe("8px") + + expect(height).toBe(96) + expect(width).toBe(188) + }) + }) }) From a78255b29f4b8b6d8f0e3864e95ed227b5007492 Mon Sep 17 00:00:00 2001 From: Peter Clay Holden Morris-Hind Date: Thu, 1 May 2025 15:47:21 +0100 Subject: [PATCH 15/48] Fix imports --- src/subtitles/embeddedsubtitles.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/subtitles/embeddedsubtitles.js b/src/subtitles/embeddedsubtitles.js index eec148331..db96ef809 100644 --- a/src/subtitles/embeddedsubtitles.js +++ b/src/subtitles/embeddedsubtitles.js @@ -1,6 +1,8 @@ import { fromXML, generateISD, renderHTML } from "smp-imsc" import DOMHelpers from "../domhelpers" import Utils from "../utils/playbackutils" +import DebugTool from "../debugger/debugtool" +import Plugins from "../plugins" function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, defaultStyleOpts) { let currentSubtitlesElement From 74364cf59cada4d5bf06ebb9b9e367bdee1ce30f Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Fri, 2 May 2025 12:32:13 +0100 Subject: [PATCH 16/48] Change call order --- src/subtitles/embeddedsubtitles.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/subtitles/embeddedsubtitles.js b/src/subtitles/embeddedsubtitles.js index db96ef809..63dfe7392 100644 --- a/src/subtitles/embeddedsubtitles.js +++ b/src/subtitles/embeddedsubtitles.js @@ -84,11 +84,11 @@ function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, } function start() { - mediaPlayer.setSubtitles(true) - customise(imscRenderOpts) if (!currentSubtitlesElement) { addCurrentSubtitlesElement() } + mediaPlayer.setSubtitles(true) + customise(imscRenderOpts) } function stop() { From 793a99ddf04f57c18ee0c506aebe9e7aa08e7d3c Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Fri, 2 May 2025 14:41:35 +0100 Subject: [PATCH 17/48] Move div creation to mse-strategy --- src/playbackstrategy/msestrategy.js | 13 +++++++++++- src/subtitles/embeddedsubtitles.js | 27 ------------------------- src/subtitles/embeddedsubtitles.test.js | 8 -------- 3 files changed, 12 insertions(+), 36 deletions(-) diff --git a/src/playbackstrategy/msestrategy.js b/src/playbackstrategy/msestrategy.js index 3e2080348..c3fe9a8e5 100644 --- a/src/playbackstrategy/msestrategy.js +++ b/src/playbackstrategy/msestrategy.js @@ -28,6 +28,7 @@ function MSEStrategy( let mediaPlayer let mediaElement + let subtitleElement const manifestType = mediaSources.time().manifestType const playerSettings = Utils.merge( @@ -553,6 +554,13 @@ function MSEStrategy( } } + function setUpSubtitleElement(playbackElement) { + subtitleElement = document.createElement("div") + subtitleElement.id = "bsp_subtitles" + subtitleElement.style.position = "absolute" + playbackElement.appendChild(subtitleElement, playbackElement.firstChild) + } + function setUpMediaElement(playbackElement) { mediaElement = mediaKind === MediaKinds.AUDIO ? document.createElement("audio") : document.createElement("video") @@ -595,7 +603,10 @@ function MSEStrategy( } ) - embeddedSubs && mediaPlayer.attachTTMLRenderingDiv(document.querySelector("#bsp_subtitles")) + if (embeddedSubs) { + setUpSubtitleElement(playbackElement) + mediaPlayer.attachTTMLRenderingDiv(document.querySelector("#bsp_subtitles")) + } modifySource(presentationTimeInSeconds) } diff --git a/src/subtitles/embeddedsubtitles.js b/src/subtitles/embeddedsubtitles.js index 63dfe7392..fe4dc1e65 100644 --- a/src/subtitles/embeddedsubtitles.js +++ b/src/subtitles/embeddedsubtitles.js @@ -5,25 +5,11 @@ import DebugTool from "../debugger/debugtool" import Plugins from "../plugins" function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, defaultStyleOpts) { - let currentSubtitlesElement let exampleSubtitlesElement - let imscRenderOpts = transformStyleOptions(defaultStyleOpts) if (autoStart) { start() - mediaPlayer.addEventCallback(this, onMediaPlayerReady) - } - - function onMediaPlayerReady() { - mediaPlayer.removeEventCallback(this, onMediaPlayerReady) - } - - function removeCurrentSubtitlesElement() { - if (currentSubtitlesElement) { - DOMHelpers.safeRemoveElement(currentSubtitlesElement) - currentSubtitlesElement = undefined - } } function removeExampleSubtitlesElement() { @@ -75,18 +61,7 @@ function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, } } - function addCurrentSubtitlesElement() { - removeCurrentSubtitlesElement() - currentSubtitlesElement = document.createElement("div") - currentSubtitlesElement.id = "bsp_subtitles" - currentSubtitlesElement.style.position = "absolute" - parentElement.appendChild(currentSubtitlesElement) - } - function start() { - if (!currentSubtitlesElement) { - addCurrentSubtitlesElement() - } mediaPlayer.setSubtitles(true) customise(imscRenderOpts) } @@ -130,8 +105,6 @@ function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, return customStyles } - addCurrentSubtitlesElement() - return { start, stop, diff --git a/src/subtitles/embeddedsubtitles.test.js b/src/subtitles/embeddedsubtitles.test.js index 78e5876df..f5e376898 100644 --- a/src/subtitles/embeddedsubtitles.test.js +++ b/src/subtitles/embeddedsubtitles.test.js @@ -78,14 +78,6 @@ describe("Embedded Subtitles", () => { }) ) }) - - it("Expect TTML rendering div to have been created", () => { - const autoStart = false - subtitles = EmbeddedSubtitles(mockMediaPlayer, autoStart, targetElement, null, {}) - - progressTime(1.5) - expect(targetElement.querySelector("#bsp_subtitles")).toBeTruthy() - }) }) describe("autoplay", () => { From e132fa33fe086021d0adf3b701e01d0b45d50099 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Fri, 2 May 2025 14:55:00 +0100 Subject: [PATCH 18/48] Add tests for div creation --- src/playbackstrategy/msestrategy.js | 2 +- src/playbackstrategy/msestrategy.test.js | 31 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/playbackstrategy/msestrategy.js b/src/playbackstrategy/msestrategy.js index c3fe9a8e5..fccc0e1a8 100644 --- a/src/playbackstrategy/msestrategy.js +++ b/src/playbackstrategy/msestrategy.js @@ -605,7 +605,7 @@ function MSEStrategy( if (embeddedSubs) { setUpSubtitleElement(playbackElement) - mediaPlayer.attachTTMLRenderingDiv(document.querySelector("#bsp_subtitles")) + mediaPlayer.attachTTMLRenderingDiv(subtitleElement) } modifySource(presentationTimeInSeconds) diff --git a/src/playbackstrategy/msestrategy.test.js b/src/playbackstrategy/msestrategy.test.js index 4a2b89660..0465deba5 100644 --- a/src/playbackstrategy/msestrategy.test.js +++ b/src/playbackstrategy/msestrategy.test.js @@ -35,6 +35,7 @@ const mockDashInstance = { time: jest.fn(), duration: jest.fn(), attachSource: jest.fn(), + attachTTMLRenderingDiv: jest.fn(), reset: jest.fn(), destroy: jest.fn(), isPaused: jest.fn(), @@ -1758,4 +1759,34 @@ describe("Media Source Extensions Playback Strategy", () => { expect(mockErrorCallback).toHaveBeenCalledWith({ code: 30, message: "videoCodec is not supported" }) }) }) + + describe("MSE embedded subtitles", () => { + beforeAll(() => { + MediaPlayer.mockReturnValue(mockDashMediaPlayer) + }) + + beforeEach(() => { + delete window.bigscreenPlayer + }) + + it("Expect MSE strategy to create subtitle div when embedded subtitles is enabled", () => { + window.bigscreenPlayer = { overrides: { embeddedSubtitles: true } } + + const mseStrategy = MSEStrategy(mockMediaSources, MediaKinds.VIDEO, playbackElement) + mseStrategy.load(null, 0) + + expect(playbackElement.querySelector("#bsp_subtitles")).toBeTruthy() + }) + + it("Expect created div to have been attached to Dash.js", () => { + window.bigscreenPlayer = { overrides: { embeddedSubtitles: true } } + + const mseStrategy = MSEStrategy(mockMediaSources, MediaKinds.VIDEO, playbackElement) + mseStrategy.load(null, 0) + + expect(mockDashInstance.attachTTMLRenderingDiv).toHaveBeenCalledWith( + playbackElement.querySelector("#bsp_subtitles") + ) + }) + }) }) From ce6d72b3f45bcab26b38e478f6e550e9af1e5b5f Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Fri, 2 May 2025 16:16:49 +0100 Subject: [PATCH 19/48] Add observer and cleanup afterwards --- src/playbackstrategy/msestrategy.js | 6 +++++- src/subtitles/embeddedsubtitles.js | 15 ++++++++++++--- src/subtitles/embeddedsubtitles.test.js | 7 +++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/playbackstrategy/msestrategy.js b/src/playbackstrategy/msestrategy.js index fccc0e1a8..ad534fd8d 100644 --- a/src/playbackstrategy/msestrategy.js +++ b/src/playbackstrategy/msestrategy.js @@ -874,9 +874,13 @@ function MSEStrategy( mediaElement.removeEventListener("ratechange", onRateChange) DOMHelpers.safeRemoveElement(mediaElement) - mediaElement = undefined } + + if (subtitleElement) { + DOMHelpers.safeRemoveElement(subtitleElement) + subtitleElement = undefined + } } function getSafelySeekableRange() { diff --git a/src/subtitles/embeddedsubtitles.js b/src/subtitles/embeddedsubtitles.js index fe4dc1e65..4279500d8 100644 --- a/src/subtitles/embeddedsubtitles.js +++ b/src/subtitles/embeddedsubtitles.js @@ -8,9 +8,18 @@ function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, let exampleSubtitlesElement let imscRenderOpts = transformStyleOptions(defaultStyleOpts) - if (autoStart) { - start() - } + const observer = new MutationObserver((_, observer) => { + const element = parentElement.querySelector("#bsp_subtitles") + if (element) { + observer.disconnect() + if (autoStart) start() + } + }) + + observer.observe(parentElement, { + childList: true, + subtree: true, + }) function removeExampleSubtitlesElement() { if (exampleSubtitlesElement) { diff --git a/src/subtitles/embeddedsubtitles.test.js b/src/subtitles/embeddedsubtitles.test.js index f5e376898..918d754ad 100644 --- a/src/subtitles/embeddedsubtitles.test.js +++ b/src/subtitles/embeddedsubtitles.test.js @@ -18,6 +18,7 @@ const mockImscDoc = { describe("Embedded Subtitles", () => { let subtitles let targetElement + let subtitleElement const mockMediaPlayer = { getCurrentTime: jest.fn(), @@ -42,6 +43,12 @@ describe("Embedded Subtitles", () => { targetElement?.remove() targetElement = document.createElement("div") + subtitleElement?.remove() + subtitleElement = document.createElement("div") + subtitleElement.id = "bsp_subtitles" + subtitleElement.style.position = "absolute" + targetElement.appendChild(subtitleElement, targetElement.firstChild) + jest.spyOn(targetElement, "clientWidth", "get").mockReturnValue(200) jest.spyOn(targetElement, "clientHeight", "get").mockReturnValue(100) jest.spyOn(targetElement, "removeChild") From 15c40631389133fb08ea0a28b2c3a0b8e9f082b0 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Fri, 2 May 2025 16:25:39 +0100 Subject: [PATCH 20/48] temp autoStart for tests --- src/subtitles/embeddedsubtitles.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/subtitles/embeddedsubtitles.js b/src/subtitles/embeddedsubtitles.js index 4279500d8..f24d1d17c 100644 --- a/src/subtitles/embeddedsubtitles.js +++ b/src/subtitles/embeddedsubtitles.js @@ -21,6 +21,8 @@ function EmbeddedSubtitles(mediaPlayer, autoStart, parentElement, mediaSources, subtree: true, }) + if (autoStart) start() + function removeExampleSubtitlesElement() { if (exampleSubtitlesElement) { DOMHelpers.safeRemoveElement(exampleSubtitlesElement) From 49bebb4a4251342ab4a7a65b5d315eea7b231327 Mon Sep 17 00:00:00 2001 From: Ryan McCartney Date: Fri, 2 May 2025 17:31:13 +0100 Subject: [PATCH 21/48] Handle enable subtitles on start --- src/playbackstrategy/msestrategy.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/playbackstrategy/msestrategy.js b/src/playbackstrategy/msestrategy.js index ad534fd8d..4a8458b38 100644 --- a/src/playbackstrategy/msestrategy.js +++ b/src/playbackstrategy/msestrategy.js @@ -29,6 +29,7 @@ function MSEStrategy( let mediaPlayer let mediaElement let subtitleElement + let subtitlesEnabled const manifestType = mediaSources.time().manifestType const playerSettings = Utils.merge( @@ -670,22 +671,15 @@ function MSEStrategy( mediaPlayer.on(DashJSEvents.GAP_JUMP, onGapJump) mediaPlayer.on(DashJSEvents.GAP_JUMP_TO_END, onGapJump) mediaPlayer.on(DashJSEvents.QUOTA_EXCEEDED, onQuotaExceeded) - mediaPlayer.on(DashJSEvents.TEXT_TRACKS_ADDED, disableTextTracks) + mediaPlayer.on(DashJSEvents.TEXT_TRACKS_ADDED, handleTextTracks) mediaPlayer.on(DashJSEvents.MANIFEST_LOADING_FINISHED, manifestLoadingFinished) mediaPlayer.on(DashJSEvents.CURRENT_TRACK_CHANGED, onCurrentTrackChanged) } - function disableTextTracks() { + function handleTextTracks() { const textTracks = mediaElement.textTracks for (let index = 0; index < textTracks.length; index++) { - textTracks[index].mode = "disabled" - } - } - - function enableTextTracks() { - const textTracks = mediaElement.textTracks - for (let index = 0; index < textTracks.length; index++) { - textTracks[index].mode = "showing" + textTracks[index].mode = subtitlesEnabled ? "showing" : "disabled" } } @@ -980,7 +974,8 @@ function MSEStrategy( getDuration, setSubtitles: (state) => { if (state) { - enableTextTracks() + subtitlesEnabled = true + handleTextTracks() } mediaPlayer.enableText(state) }, From 3cbb3b66759f403912adf353b8482f9a71e973e6 Mon Sep 17 00:00:00 2001 From: Peter Clay Holden Morris-Hind Date: Tue, 6 May 2025 11:41:49 +0100 Subject: [PATCH 22/48] Fix Autostart --- index.html | 7 +++++-- src/playercomponent.js | 9 ++++++++- src/subtitles/embeddedsubtitles.js | 15 +-------------- src/subtitles/embeddedsubtitles.test.js | 2 +- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/index.html b/index.html index e7ab8b68e..5a843c7cd 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,10 @@