diff --git a/docs/es-modules/fluid.html b/docs/es-modules/fluid.html
index 464d03183..fbc70435d 100644
--- a/docs/es-modules/fluid.html
+++ b/docs/es-modules/fluid.html
@@ -39,7 +39,7 @@
Fluid Layouts
diff --git a/src/index.all.js b/src/index.all.js
index 197791261..59dba7078 100644
--- a/src/index.all.js
+++ b/src/index.all.js
@@ -7,17 +7,18 @@
import cloudinary from './index.js';
-export * from './index.js';
-export * from './plugins/adaptive-streaming/adaptive-streaming.js';
-export * from './plugins/chapters/chapters.js';
-export * from './plugins/colors/colors.js';
-export * from './plugins/ima/ima.js';
-export * from './plugins/playlist/playlist.js';
-export * from './plugins/interaction-areas/interaction-areas.service.js';
-export * from './plugins/visual-search/visual-search.js';
-export * from './plugins/text-tracks-manager/index.js';
-export * from './plugins/share/share.js';
-export * from './components/shoppable-bar/shoppable-widget.js';
-export * from './components/recommendations-overlay/recommendations-overlay.js';
+// Import plugin implementations so webpack bundles them in /all.js
+import './plugins/adaptive-streaming/adaptive-streaming.js';
+import './plugins/chapters/chapters.js';
+import './plugins/colors/colors.js';
+import './plugins/ima/ima.js';
+import './plugins/playlist/playlist.js';
+import './plugins/interaction-areas/interaction-areas.service.js';
+import './plugins/visual-search/visual-search.js';
+import './plugins/share/share.js';
+import './plugins/text-tracks-manager/text-tracks-manager.js';
+import './components/shoppable-bar/shoppable-widget.js';
+import './components/recommendations-overlay/recommendations-overlay.js';
+export * from './index.js';
export default cloudinary;
diff --git a/src/index.es.js b/src/index.es.js
deleted file mode 100644
index f19be2607..000000000
--- a/src/index.es.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// This file is bundled as `cld-video-player.js` to be imported as a tree-shaken module.
-// It is the default export of the Cloudinary Video Player.
-
-// Usage:
-// import cloudinary from 'cloudinary-video-player';
-// Or:
-// import { videoPlayer } from "cloudinary-video-player";
-
-// Other modules can be imported like that:
-// import dash from 'cloudinary-video-player/dash';
-
-import cloudinary from './index.js';
-
-export const videoPlayer = cloudinary.videoPlayer;
-export const videoPlayers = cloudinary.videoPlayers;
-
-export const player = cloudinary.player;
-
-export default cloudinary;
diff --git a/src/plugins/text-tracks-manager/index.js b/src/plugins/text-tracks-manager/index.js
index b874eb24e..ca19fb938 100644
--- a/src/plugins/text-tracks-manager/index.js
+++ b/src/plugins/text-tracks-manager/index.js
@@ -1,194 +1,11 @@
-import { utf8ToBase64 } from '../../utils/utf8Base64';
-import { getCloudinaryUrlPrefix } from '../cloudinary/common';
-import { transcriptParser } from './parsers/transcriptParser';
-import { srtParser } from './parsers/srtParser';
-import { vttParser } from './parsers/vttParser';
-import { addTextTrackCues, fetchFileContent, refreshTextTrack, removeAllTextTrackCues } from './utils';
-
-const getTranscriptionFileUrl = (urlPrefix, deliveryType, publicId, languageCode = null) =>
- `${urlPrefix}/_applet_/video_service/transcription/${deliveryType}/${languageCode ? `${languageCode}/` : ''}${utf8ToBase64(publicId)}.transcript`;
-
-function textTracksManager() {
+export default async function lazyTextTracksManagerPlugin() {
const player = this;
- const textTracksData = new WeakMap();
- let activeTrack = null;
-
- const removeAllTextTracks = () => {
- const currentTracks = player.remoteTextTracks();
- if (currentTracks) {
- for (let i = currentTracks.tracks_.length - 1; i >= 0; i--) {
- player.removeRemoteTextTrack(currentTracks.tracks_[i]);
- }
- }
- };
-
- const createTextTrackData = (textTrack, loadMethod) => {
- const controller = new AbortController();
- textTracksData.set(textTrack, {
- status: 'idle',
- load: async () => {
- const { status } = textTracksData.get(textTrack);
- if (status === 'idle') {
- await loadMethod(controller.signal);
- refreshTextTrack(textTrack);
- }
- },
- abortLoading: () => {
- const { status } = textTracksData.get(textTrack);
- if (status === 'pending') {
- controller.abort();
- }
- },
- });
- };
-
- const updateTextTrackData = (textTrack, dataToUpdate) => {
- const existingData = textTracksData.get(textTrack);
- textTracksData.set(textTrack, {
- ...existingData,
- ...dataToUpdate,
- });
- };
- const updateTextTrackStatusToPending = (textTrack) => updateTextTrackData(textTrack, { status: 'pending' });
- const updateTextTrackStatusToSuccess = (textTrack) => updateTextTrackData(textTrack, { status: 'success' });
- const updateTextTrackStatusToError = (textTrack, error) => updateTextTrackData(textTrack, { status: 'error', error });
- const updateTextTrackStatusToApplied = (textTrack) => updateTextTrackData(textTrack, { status: 'applied' });
-
- const addTextTrack = (type, config) => {
- const {
- kind = type === 'transcript' ? 'captions' : 'subtitles',
- label = type === 'transcript' ? 'Captions' : 'Subtitles',
- default: isDefault,
- srclang,
- src,
- } = config;
-
- if (type === 'transcript') {
- player.textTrackDisplay.el().classList.add('cld-paced-text-tracks');
- }
-
- const { track } = player.addRemoteTextTrack({
- kind,
- label,
- srclang,
- default: isDefault,
- mode: isDefault ? 'showing' : 'disabled',
- });
-
- const createParser = () => {
- if (type === 'srt') return srtParser;
- if (type === 'vtt') return vttParser;
- return (text) => transcriptParser(text, {
- maxWords: config.maxWords,
- wordHighlight: config.wordHighlight,
- timeOffset: config.timeOffset ?? 0,
- });
- };
-
- const createSourceUrl = () => {
- if (src) return src;
- if (type !== 'transcript') return undefined;
-
- const source = player.cloudinary.source();
- const publicId = source.publicId();
- const deliveryType = source.resourceConfig().type;
- const urlPrefix = getCloudinaryUrlPrefix(player.cloudinary.cloudinaryConfig());
- const baseUrl = getTranscriptionFileUrl(urlPrefix, deliveryType, publicId);
- const localizedUrl = srclang ? getTranscriptionFileUrl(urlPrefix, deliveryType, publicId, srclang) : null;
-
- return localizedUrl ? localizedUrl : baseUrl;
- };
-
- createTextTrackData(track, async (signal) => {
- updateTextTrackStatusToPending(track);
-
- const sourceUrl = createSourceUrl();
- const response = await fetchFileContent(
- sourceUrl,
- {
- signal,
- polling: type === 'transcript' && !src,
- interval: 2000,
- maxAttempts: 10,
- responseStatusAsPending: 202,
- onSuccess: () => updateTextTrackStatusToSuccess(track),
- onError: (error) => {
- updateTextTrackStatusToError(track, error);
- console.warn(`[${track.label}] Text track could not be loaded`);
- },
-
- }
- );
-
- if (response) {
- const parser = createParser();
- const data = await parser(response);
- removeAllTextTrackCues(track);
- addTextTrackCues(track, data);
- updateTextTrackStatusToApplied(track);
- }
- });
- };
-
-
- const addTextTracks = (textTracks) => {
- textTracks.forEach(textTrackConfig => {
- if (textTrackConfig.src && textTrackConfig.src.endsWith('.vtt')) {
- addTextTrack('vtt', textTrackConfig);
- } else if (textTrackConfig.src && textTrackConfig.src.endsWith('.srt')) {
- addTextTrack('srt', textTrackConfig);
- } else if (!textTrackConfig.src || textTrackConfig.src.endsWith('.transcript')) {
- addTextTrack('transcript', textTrackConfig);
- }
- });
-
- const defaultTextTrack = Array.from(player.remoteTextTracks()).find((textTrack) => textTrack.default);
- if (defaultTextTrack) {
- onChangeActiveTrack(defaultTextTrack);
- }
- };
-
- const onChangeActiveTrack = (textTrack) => {
- const prevActiveTrack = activeTrack;
- activeTrack = textTrack;
-
- const prevTextTrackData = textTracksData.get(prevActiveTrack);
- if (prevTextTrackData) {
- prevTextTrackData.abortLoading();
- }
-
- const selectedTextTrackData = textTracksData.get(activeTrack);
- if (selectedTextTrackData) {
- selectedTextTrackData.load();
- }
- };
-
- player.on('texttrackchange', () => {
- const textTracks = player.textTracks();
- let newActiveTrack = null;
-
- for (let i = 0; i < textTracks.length; i++) {
- const track = textTracks[i];
-
- if (track.mode === 'showing') {
- newActiveTrack = track;
- break;
- }
- }
-
- if (activeTrack !== newActiveTrack) {
- onChangeActiveTrack(newActiveTrack);
- }
- });
-
- return {
- removeAllTextTracks,
- addTextTracks: (...args) => {
- player.one('loadedmetadata', () => {
- addTextTracks(...args);
- });
- },
- };
+ try {
+ const { default: textTracksManager } = await import(
+ /* webpackChunkName: "text-tracks" */ './text-tracks-manager'
+ );
+ return textTracksManager.call(player);
+ } catch (error) {
+ console.error('Failed to load text tracks manager plugin:', error);
+ }
}
-
-export default textTracksManager;
diff --git a/src/plugins/text-tracks-manager/text-tracks-manager.js b/src/plugins/text-tracks-manager/text-tracks-manager.js
new file mode 100644
index 000000000..b2b806039
--- /dev/null
+++ b/src/plugins/text-tracks-manager/text-tracks-manager.js
@@ -0,0 +1,195 @@
+import { utf8ToBase64 } from '../../utils/utf8Base64';
+import { getCloudinaryUrlPrefix } from '../cloudinary/common';
+import { transcriptParser } from './parsers/transcriptParser';
+import { srtParser } from './parsers/srtParser';
+import { vttParser } from './parsers/vttParser';
+import { addTextTrackCues, fetchFileContent, refreshTextTrack, removeAllTextTrackCues } from './utils';
+
+const getTranscriptionFileUrl = (urlPrefix, deliveryType, publicId, languageCode = null) =>
+ `${urlPrefix}/_applet_/video_service/transcription/${deliveryType}/${languageCode ? `${languageCode}/` : ''}${utf8ToBase64(publicId)}.transcript`;
+
+function textTracksManager() {
+ const player = this;
+ const textTracksData = new WeakMap();
+ let activeTrack = null;
+
+ const removeAllTextTracks = () => {
+ const currentTracks = player.remoteTextTracks();
+ if (currentTracks) {
+ for (let i = currentTracks.tracks_.length - 1; i >= 0; i--) {
+ player.removeRemoteTextTrack(currentTracks.tracks_[i]);
+ }
+ }
+ };
+
+ const createTextTrackData = (textTrack, loadMethod) => {
+ const controller = new AbortController();
+ textTracksData.set(textTrack, {
+ status: 'idle',
+ load: async () => {
+ const { status } = textTracksData.get(textTrack);
+ if (status === 'idle') {
+ await loadMethod(controller.signal);
+ refreshTextTrack(textTrack);
+ }
+ },
+ abortLoading: () => {
+ const { status } = textTracksData.get(textTrack);
+ if (status === 'pending') {
+ controller.abort();
+ }
+ },
+ });
+ };
+
+ const updateTextTrackData = (textTrack, dataToUpdate) => {
+ const existingData = textTracksData.get(textTrack);
+ textTracksData.set(textTrack, {
+ ...existingData,
+ ...dataToUpdate,
+ });
+ };
+ const updateTextTrackStatusToPending = (textTrack) => updateTextTrackData(textTrack, { status: 'pending' });
+ const updateTextTrackStatusToSuccess = (textTrack) => updateTextTrackData(textTrack, { status: 'success' });
+ const updateTextTrackStatusToError = (textTrack, error) => updateTextTrackData(textTrack, { status: 'error', error });
+ const updateTextTrackStatusToApplied = (textTrack) => updateTextTrackData(textTrack, { status: 'applied' });
+
+ const addTextTrack = (type, config) => {
+ const {
+ kind = type === 'transcript' ? 'captions' : 'subtitles',
+ label = type === 'transcript' ? 'Captions' : 'Subtitles',
+ default: isDefault,
+ srclang,
+ src,
+ } = config;
+
+ if (type === 'transcript') {
+ player.textTrackDisplay.el().classList.add('cld-paced-text-tracks');
+ }
+
+ const { track } = player.addRemoteTextTrack({
+ kind,
+ label,
+ srclang,
+ default: isDefault,
+ mode: isDefault ? 'showing' : 'disabled',
+ });
+
+ const createParser = () => {
+ if (type === 'srt') return srtParser;
+ if (type === 'vtt') return vttParser;
+ return (text) => transcriptParser(text, {
+ maxWords: config.maxWords,
+ wordHighlight: config.wordHighlight,
+ timeOffset: config.timeOffset ?? 0,
+ });
+ };
+
+ const createSourceUrl = () => {
+ if (src) return src;
+ if (type !== 'transcript') return undefined;
+
+ const source = player.cloudinary.source();
+ const publicId = source.publicId();
+ const deliveryType = source.resourceConfig().type;
+ const urlPrefix = getCloudinaryUrlPrefix(player.cloudinary.cloudinaryConfig());
+ const baseUrl = getTranscriptionFileUrl(urlPrefix, deliveryType, publicId);
+ const localizedUrl = srclang ? getTranscriptionFileUrl(urlPrefix, deliveryType, publicId, srclang) : null;
+
+ return localizedUrl ? localizedUrl : baseUrl;
+ };
+
+ createTextTrackData(track, async (signal) => {
+ updateTextTrackStatusToPending(track);
+
+ const sourceUrl = createSourceUrl();
+ const response = await fetchFileContent(
+ sourceUrl,
+ {
+ signal,
+ polling: type === 'transcript' && !src,
+ interval: 2000,
+ maxAttempts: 10,
+ responseStatusAsPending: 202,
+ onSuccess: () => updateTextTrackStatusToSuccess(track),
+ onError: (error) => {
+ updateTextTrackStatusToError(track, error);
+ console.warn(`[${track.label}] Text track could not be loaded`);
+ },
+
+ }
+ );
+
+ if (response) {
+ const parser = createParser();
+ const data = await parser(response);
+ removeAllTextTrackCues(track);
+ addTextTrackCues(track, data);
+ updateTextTrackStatusToApplied(track);
+ }
+ });
+ };
+
+
+ const addTextTracks = (textTracks) => {
+ textTracks.forEach(textTrackConfig => {
+ if (textTrackConfig.src && textTrackConfig.src.endsWith('.vtt')) {
+ addTextTrack('vtt', textTrackConfig);
+ } else if (textTrackConfig.src && textTrackConfig.src.endsWith('.srt')) {
+ addTextTrack('srt', textTrackConfig);
+ } else if (!textTrackConfig.src || textTrackConfig.src.endsWith('.transcript')) {
+ addTextTrack('transcript', textTrackConfig);
+ }
+ });
+
+ const defaultTextTrack = Array.from(player.remoteTextTracks()).find((textTrack) => textTrack.default);
+ if (defaultTextTrack) {
+ onChangeActiveTrack(defaultTextTrack);
+ }
+ };
+
+ const onChangeActiveTrack = (textTrack) => {
+ const prevActiveTrack = activeTrack;
+ activeTrack = textTrack;
+
+ const prevTextTrackData = textTracksData.get(prevActiveTrack);
+ if (prevTextTrackData) {
+ prevTextTrackData.abortLoading();
+ }
+
+ const selectedTextTrackData = textTracksData.get(activeTrack);
+ if (selectedTextTrackData) {
+ selectedTextTrackData.load();
+ }
+ };
+
+ player.on('texttrackchange', () => {
+ const textTracks = player.textTracks();
+ let newActiveTrack = null;
+
+ for (let i = 0; i < textTracks.length; i++) {
+ const track = textTracks[i];
+
+ if (track.mode === 'showing') {
+ newActiveTrack = track;
+ break;
+ }
+ }
+
+ if (activeTrack !== newActiveTrack) {
+ onChangeActiveTrack(newActiveTrack);
+ }
+ });
+
+ return {
+ removeAllTextTracks,
+ addTextTracks: (...args) => {
+ player.one('loadedmetadata', () => {
+ addTextTracks(...args);
+ });
+ },
+ };
+}
+
+export default textTracksManager;
+
diff --git a/src/video-player.js b/src/video-player.js
index bdf045c37..5185fcfb2 100644
--- a/src/video-player.js
+++ b/src/video-player.js
@@ -209,6 +209,10 @@ class VideoPlayer extends Utils.mixin(Eventable) {
}
setTextTracks(conf) {
+ if (!this.textTracksManager) {
+ return;
+ }
+
this.textTracksManager.removeAllTextTracks();
if (conf) {
@@ -376,9 +380,11 @@ class VideoPlayer extends Utils.mixin(Eventable) {
}
_initTextTracks () {
- this.textTracksManager = this.videojs.textTracksManager();
- this.videojs.on(PLAYER_EVENT.CLD_SOURCE_CHANGED, (e, { source }) => {
- if (source?._textTracks) {
+ this.videojs.on(PLAYER_EVENT.CLD_SOURCE_CHANGED, async (e, { source }) => {
+ if (source?._textTracks && this.videojs.textTracksManager) {
+ if (!this.textTracksManager) {
+ this.textTracksManager = await this.videojs.textTracksManager();
+ }
this.setTextTracks(source._textTracks);
}
});
diff --git a/webpack/es6.config.js b/webpack/es6.config.js
index dbe253244..e84e1c74b 100644
--- a/webpack/es6.config.js
+++ b/webpack/es6.config.js
@@ -11,7 +11,7 @@ module.exports = merge(webpackCommon, {
mode: 'production',
entry: {
- 'cld-video-player': './index.es.js', // default
+ 'cld-video-player': './index.js', // default
'videoPlayer': './index.videoPlayer.js',
'player': './index.player.js',
'all': './index.all.js'