diff --git a/docs/es-modules/subtitles-and-captions.html b/docs/es-modules/subtitles-and-captions.html
index c1e0f7c0..9e2e048e 100644
--- a/docs/es-modules/subtitles-and-captions.html
+++ b/docs/es-modules/subtitles-and-captions.html
@@ -104,6 +104,17 @@
Karaoke player
width="500"
>
+ Translated Transcript
+
+
+
Full documentationKaraoke player
import 'cloudinary-video-player/playlist';
const player = videoPlayer('player', {
- cloudName: 'demo'
+ cloudName: 'prod'
});
- player.source('video-player/stubhub', {
- textTracks: {
- captions: {
- label: 'English captions',
- language: 'en',
- default: true,
- url: 'https://res.cloudinary.com/demo/raw/upload/v1636972013/video-player/vtt/Meetup_english.vtt'
- },
- subtitles: [
- {
- label: 'German subtitles',
- language: 'de',
- url: 'https://res.cloudinary.com/demo/raw/upload/v1636970250/video-player/vtt/Meetup_german.vtt'
+ player.source(
+ 'video/examples/big_buck_bunny_trailer_720p',
+ {
+ info: { title: 'SRT & VTT from URL' },
+ textTracks: {
+ options: {
+ theme: "videojs-default"
},
- {
- label: 'Russian subtitles',
- language: 'ru',
- url: 'https://res.cloudinary.com/demo/raw/upload/v1636970275/video-player/vtt/Meetup_russian.vtt'
- }
- ]
+ captions: {
+ label: 'VTT from URL',
+ default: true,
+ url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.vtt'
+ },
+ subtitles: [
+ {
+ label: 'SRT from URL',
+ url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.srt'
+ }
+ ]
+ }
}
- });
+ );
// Playlist
const playlist = videoPlayer('playlist', {
@@ -202,7 +213,7 @@
Karaoke player
playlist.playlist(playlistSources, playlistOptions);
// Paced
- const pacedPlayer = cloudinary.videoPlayer('paced', {
+ const pacedPlayer = videoPlayer('paced', {
cloudName: 'prod'
});
@@ -250,7 +261,7 @@ Karaoke player
});
// Karaoke
- const karaokePlayer = cloudinary.videoPlayer('karaoke', {
+ const karaokePlayer = videoPlayer('karaoke', {
cloudName: 'prod'
});
@@ -282,7 +293,7 @@ Karaoke player
});
// Auto-translated transcript
- const translatedTranscriptPlayer = cloudinary.videoPlayer('translated-transcript', {
+ const translatedTranscriptPlayer = videoPlayer('translated-transcript', {
cloudName: 'prod'
});
diff --git a/docs/subtitles-and-captions.html b/docs/subtitles-and-captions.html
index cde74bb7..c115377d 100644
--- a/docs/subtitles-and-captions.html
+++ b/docs/subtitles-and-captions.html
@@ -28,29 +28,26 @@
window.addEventListener('load', function(){
var player = cloudinary.videoPlayer('player', {
- cloud_name: 'demo'
+ cloud_name: 'prod'
});
player.source(
- 'video-player/stubhub',
+ 'video/examples/big_buck_bunny_trailer_720p',
{
+ info: { title: 'SRT & VTT from URL' },
textTracks: {
+ options: {
+ theme: "videojs-default"
+ },
captions: {
- label: 'English captions',
- language: 'en',
+ label: 'VTT from URL',
default: true,
- url: 'https://res.cloudinary.com/demo/raw/upload/v1636972013/video-player/vtt/Meetup_english.vtt'
+ url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.vtt'
},
subtitles: [
{
- label: 'German subtitles',
- language: 'de',
- url: 'https://res.cloudinary.com/demo/raw/upload/v1636970250/video-player/vtt/Meetup_german.vtt'
- },
- {
- label: 'Russian subtitles',
- language: 'ru',
- url: 'https://res.cloudinary.com/demo/raw/upload/v1636970275/video-player/vtt/Meetup_russian.vtt'
+ label: 'SRT from URL',
+ url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.srt'
}
]
}
@@ -353,29 +350,26 @@ Example Code:
// Initialize players
var player = cloudinary.videoPlayer('player', {
- cloud_name: 'demo'
+ cloud_name: 'prod'
});
player.source(
- 'video-player/stubhub',
+ 'video/examples/big_buck_bunny_trailer_720p',
{
+ info: { title: 'SRT & VTT from URL' },
textTracks: {
+ options: {
+ theme: "videojs-default"
+ },
captions: {
- label: 'English captions',
- language: 'en',
+ label: 'VTT from URL',
default: true,
- url: 'https://res.cloudinary.com/demo/raw/upload/v1636972013/video-player/vtt/Meetup_english.vtt'
+ url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.vtt'
},
subtitles: [
{
- label: 'German subtitles',
- language: 'de',
- url: 'https://res.cloudinary.com/demo/raw/upload/v1636970250/video-player/vtt/Meetup_german.vtt'
- },
- {
- label: 'Russian subtitles',
- language: 'ru',
- url: 'https://res.cloudinary.com/demo/raw/upload/v1636970275/video-player/vtt/Meetup_russian.vtt'
+ label: 'SRT from URL',
+ url: 'https://res.cloudinary.com/prod/raw/upload/video/examples/big_buck_bunny_trailer_720p.srt'
}
]
}
diff --git a/package-lock.json b/package-lock.json
index 5fa186f3..1cc66c93 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"cloudinary-video-analytics": "1.7.1",
"cloudinary-video-player-profiles": "1.1.0",
"lodash": "^4.17.21",
+ "srt-parser-2": "^1.2.3",
"uuid": "^10.0.0",
"video.js": "^8.17.1",
"videojs-contrib-ads": "^7.5.2",
@@ -17014,6 +17015,18 @@
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
},
+ "node_modules/srt-parser-2": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/srt-parser-2/-/srt-parser-2-1.2.3.tgz",
+ "integrity": "sha512-dANP1AyJTI503H0/kXwRza+7QxDB3BqeFvEKTF4MI9lQcBe8JbRUQTKVIGzGABJCwBovEYavZ2Qsdm/s8XKz8A==",
+ "license": "MIT",
+ "bin": {
+ "srt-parser-2": "bin/index.js"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
diff --git a/package.json b/package.json
index 5dc4012e..cbce58f7 100644
--- a/package.json
+++ b/package.json
@@ -82,6 +82,7 @@
"cloudinary-video-analytics": "1.7.1",
"cloudinary-video-player-profiles": "1.1.0",
"lodash": "^4.17.21",
+ "srt-parser-2": "^1.2.3",
"uuid": "^10.0.0",
"video.js": "^8.17.1",
"videojs-contrib-ads": "^7.5.2",
diff --git a/src/plugins/index.js b/src/plugins/index.js
index 684421a3..90e26c1c 100644
--- a/src/plugins/index.js
+++ b/src/plugins/index.js
@@ -20,6 +20,7 @@ import chapters from './chapters';
import imaPlugin from './ima';
import playlist from './playlist';
import shoppable from './shoppable-plugin';
+import srtTextTracks from './srt-text-tracks';
import styledTextTracks from './styled-text-tracks';
import interactionAreas from './interaction-areas';
@@ -40,6 +41,7 @@ const plugins = {
imaPlugin,
playlist,
shoppable,
+ srtTextTracks,
styledTextTracks,
interactionAreas
};
diff --git a/src/plugins/paced-transcript/index.js b/src/plugins/paced-transcript/index.js
index 6be26564..ab4f6c61 100644
--- a/src/plugins/paced-transcript/index.js
+++ b/src/plugins/paced-transcript/index.js
@@ -52,7 +52,7 @@ function pacedTranscript(config) {
transcriptResponse = await fallbackFetch(`${basePath}.transcript`);
}
}
- if (!transcriptResponse.ok) return;
+ if (!transcriptResponse?.ok) return;
const transcriptData = await transcriptResponse.json();
const captions = parseTranscript(transcriptData);
diff --git a/src/plugins/srt-text-tracks/index.js b/src/plugins/srt-text-tracks/index.js
new file mode 100644
index 00000000..19dc9c79
--- /dev/null
+++ b/src/plugins/srt-text-tracks/index.js
@@ -0,0 +1,10 @@
+import srtTextTracks from './srt-text-tracks';
+
+export default async function srtTextTracksPlugin(config) {
+ const player = this;
+ try {
+ player.ready(() => srtTextTracks(config, player));
+ } catch (error) {
+ console.error('Failed to load plugin:', error);
+ }
+}
diff --git a/src/plugins/srt-text-tracks/srt-text-tracks.js b/src/plugins/srt-text-tracks/srt-text-tracks.js
new file mode 100644
index 00000000..4859a733
--- /dev/null
+++ b/src/plugins/srt-text-tracks/srt-text-tracks.js
@@ -0,0 +1,60 @@
+import srtParser2 from 'srt-parser-2';
+
+function srtTextTracks(config, player) {
+ // Load the SRT file and convert it to WebVTT
+ const initSRT = async () => {
+ let srtResponse;
+ if (config.src) {
+ try {
+ srtResponse = await fetch(config.src);
+ if (!srtResponse.ok) {
+ throw new Error(`Failed fetching from ${config.src} with status code ${srtResponse.status}`);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ if (!srtResponse.ok) return;
+
+ const srtData = await srtResponse.text();
+ const webvttCues = srt2webvtt(srtData); // Get the array of cues
+
+ const srtTrack = player.addRemoteTextTrack({
+ kind: config.kind || 'subtitles',
+ label: config.label || 'Subtitles',
+ srclang: config.srclang,
+ default: config.default,
+ mode: config.default ? 'showing' : 'disabled'
+ });
+
+ // required for Safari to display the captions
+ // https://github.com/videojs/video.js/issues/8519
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ // Add the WebVTT data to the track
+ webvttCues.forEach(cue => {
+ if (cue) {
+ srtTrack.track.addCue(new VTTCue(cue.startTime, cue.endTime, cue.text));
+ }
+ });
+ };
+
+ player.one('loadedmetadata', () => {
+ initSRT();
+ });
+}
+
+// SRT parser
+const srt2webvtt = data => {
+ const SRTParser = new srtParser2();
+
+ const cues = SRTParser.fromSrt(data);
+
+ return cues.map(cue => ({
+ startTime: cue.startSeconds,
+ endTime: cue.endSeconds,
+ text: cue.text
+ }));
+};
+
+export default srtTextTracks;
diff --git a/src/utils/cloudinary.js b/src/utils/cloudinary.js
index cf7e83a1..4ecf9108 100644
--- a/src/utils/cloudinary.js
+++ b/src/utils/cloudinary.js
@@ -107,6 +107,8 @@ const addTextTracks = (tracks, videojs) => {
videojs.addRemoteTextTrack(track, true);
}
});
+ } else if (track.src && track.src.endsWith('.srt')) {
+ videojs.srtTextTracks(track);
} else if (videojs.pacedTranscript && (!track.src || track.src.endsWith('.transcript'))) {
videojs.pacedTranscript(track);
}
diff --git a/src/utils/get-analytics-player-options.js b/src/utils/get-analytics-player-options.js
index ffb33eec..7c9ab48f 100644
--- a/src/utils/get-analytics-player-options.js
+++ b/src/utils/get-analytics-player-options.js
@@ -23,12 +23,15 @@ const getTranscriptOptions = (textTracks = {}) => {
const tracksArr = [textTracks.captions, ...textTracks.subtitles];
return {
textTracks: hasConfig(textTracks),
+ textTracksLength: tracksArr.length,
+ textTracksOptions: hasConfig(textTracks.options) || Object.keys(textTracks.options).join(','),
pacedTextTracks: hasConfig(textTracks) && JSON.stringify(textTracks || {}).includes('"maxWords":') || null,
wordHighlight: hasConfig(textTracks) && JSON.stringify(textTracks || {}).includes('"wordHighlight":') || null,
+ transcriptLanguages: tracksArr.filter((track) => !track.url).map((track) => track.language || '').join(',') || null,
transcriptAutoLoaded: tracksArr.some((track) => !track.url) || null,
transcriptFromURl: tracksArr.some((track) => track.url?.endsWith('.transcript')) || null,
- transcriptLanguages: tracksArr.filter((track) => !track.url).map((track) => track.language || '').join(',') || null,
- vttFromUrl: tracksArr.some((track) => track.url?.endsWith('.vtt')) || null
+ vttFromUrl: tracksArr.some((track) => track.url?.endsWith('.vtt')) || null,
+ srtFromUrl: tracksArr.some((track) => track.url?.endsWith('.srt')) || null
};
};