Skip to content

Commit

Permalink
Added subtitle event handling for manual rendering the subtitles (#4360)
Browse files Browse the repository at this point in the history
* Added subtitle event handling for manual rendering the subtitles

This adds two new media player events: `CUE_ENTER` and `CUE_EXIT`.
With these events you can render the subtitles without relying on
the browser. This makes it possible to fully customize the UI for
the subtitles. To enable the events, set the `fireCueEvents` setting
to true.

For details check out the example in `samples/captioning/events.html`.
  • Loading branch information
bitboxer committed Feb 6, 2024
1 parent 68d82ce commit db643ad
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 47 deletions.
16 changes: 16 additions & 0 deletions index.d.ts
Expand Up @@ -1043,6 +1043,7 @@ declare namespace dashjs {
},
text?: {
defaultEnabled?: boolean,
dispatchForManualRendering?: boolean,
extendSegmentedCues?: boolean,
imsc?: {
displayForcedOnlyMode?: boolean,
Expand Down Expand Up @@ -1533,6 +1534,8 @@ declare namespace dashjs {
CAPTION_RENDERED: 'captionRendered';
CAPTION_CONTAINER_RESIZE: 'captionContainerResize';
CONFORMANCE_VIOLATION: 'conformanceViolation';
CUE_ENTER: 'cueEnter';
CUE_EXIT: 'cueExit';
DVB_FONT_DOWNLOAD_ADDED: 'dvbFontDownloadAdded';
DVB_FONT_DOWNLOAD_COMPLETE: 'dvbFontDownloadComplete';
DVB_FONT_DOWNLOAD_FAILED: 'dvbFontDownloadFailed';
Expand Down Expand Up @@ -1982,6 +1985,19 @@ declare namespace dashjs {
content: object;
}

export interface CueEnterEvent extends Event {
type: MediaPlayerEvents['CUE_ENTER'];
id: string,
text: string,
start: number,
end: number
}

export interface CueExitEvent extends Event {
type: MediaPlayerEvents['CUE_EXIT'];
id: string,
}

export interface AdaptationSetRemovedNoCapabilitiesEvent extends Event {
type: MediaPlayerEvents['ADAPTATION_SET_REMOVED_NO_CAPABILITIES'];
adaptationSet: object;
Expand Down
216 changes: 216 additions & 0 deletions samples/captioning/events.html
@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Captions Event Sample</title>
<script src="../../contrib/videojs-vtt.js/vtt.min.js"></script>
<script
class="code"
src="../../contrib/akamai/controlbar/ControlBar.js"
></script>
<script src="../../dist/dash.all.debug.js"></script>

<!-- Bootstrap core CSS -->
<link href="../lib/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="../lib/main.css" rel="stylesheet" />

<link
rel="stylesheet"
href="../../contrib/akamai/controlbar/controlbar.css"
/>

<style>
video {
width: 100%;
}

.dash-video-player {
position: relative; /* This position relative is needed to position the menus */
margin: 0 auto;
line-height: 1;
}
</style>

<script class="code">
var CAPTION_URL =
"https://dash.akamaized.net/akamai/test/caption_test/ElephantsDream/elephants_dream_480p_heaac5_1_https.mpd",
video,
player;

function init() {
var url = CAPTION_URL,

video = document.querySelector("video");

player = dashjs.MediaPlayer().create();
player.updateSettings({
streaming: {
text: {
dispatchForManualRendering: true,
webvtt: {
customRenderingEnabled: true
}
}
}
})
player.initialize(video, url, true);

player.on("playbackPaused", function (e) {
console.log("playbackPaused");
});
player.on("cueEnter", function (e) {
console.log("cueEnter", e);
const subtitle = document.createElement("p")
subtitle.setAttribute("id", "subtitle-" + e.cueID)
subtitle.innerHTML = e.text;
document.getElementById("subtitles").appendChild(subtitle);
});
player.on("cueExit", function (e) {
console.log("cueExit ", e);
document.getElementById("subtitle-" + e.cueID).remove();
});

controlbar = new ControlBar(player);
controlbar.initialize();
}
</script>
</head>
<body>
<main>
<div class="container py-4">
<header class="pb-3 mb-4 border-bottom">
<img
class=""
src="../lib/img/dashjs-logo.png"
width="200"
/>
</header>
<div class="row">
<div class="col-md-4">
<div class="h-50 p-5 bg-light border rounded-3">
<h3>Subtitle Event Handling</h3>
<p>
Example showing how to consume subtitle events raised by
dash.js. This way you can render the subtitles yourself.
</p>
</div>
<div class="h-45 mt-5 p-5 bg-light border rounded-3">
<h4>Current Subtitle</h4>
<div id="subtitles">

</div>
</div>
</div>
<div class="col-md-8">
<div class="dash-video-player code">
<div class="videoContainer" id="videoContainer">
<video preload="auto" autoplay></video>
<div
id="videoController"
class="video-controller unselectable"
>
<div
id="playPauseBtn"
class="btn-play-pause"
title="Play/Pause"
>
<span
id="iconPlayPause"
class="icon-play"
></span>
</div>
<span id="videoTime" class="time-display"
>00:00:00</span
>
<div
id="fullscreenBtn"
class="btn-fullscreen control-icon-layout"
title="Fullscreen"
>
<span
class="icon-fullscreen-enter"
></span>
</div>
<div
id="bitrateListBtn"
class="control-icon-layout"
title="Bitrate List"
>
<span class="icon-bitrate"></span>
</div>
<input
type="range"
id="volumebar"
class="volumebar"
value="1"
min="0"
max="1"
step=".01"
/>
<div
id="muteBtn"
class="btn-mute control-icon-layout"
title="Mute"
>
<span
id="iconMute"
class="icon-mute-off"
></span>
</div>
<div
id="trackSwitchBtn"
class="control-icon-layout"
title="A/V Tracks"
>
<span class="icon-tracks"></span>
</div>
<div
id="captionBtn"
class="btn-caption control-icon-layout"
title="Closed Caption"
>
<span class="icon-caption"></span>
</div>
<span
id="videoDuration"
class="duration-display"
>00:00:00</span
>
<div class="seekContainer">
<div
id="seekbar"
class="seekbar seekbar-complete"
>
<div
id="seekbar-buffer"
class="seekbar seekbar-buffer"
></div>
<div
id="seekbar-play"
class="seekbar seekbar-play"
></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="code-output"></div>
</div>
</div>
<footer class="pt-3 mt-4 text-muted border-top">
&copy; DASH-IF
</footer>
</div>
</main>
<script>
document.addEventListener("DOMContentLoaded", function () {
init();
});
</script>
<script src="../highlighter.js"></script>
</body>
</html>
13 changes: 13 additions & 0 deletions samples/samples.json
Expand Up @@ -512,6 +512,19 @@
"Video",
"Audio"
]
},
{
"title": "Subtitle Event Handling",
"description": "Example showing how to consume subtitle events raised by dash.js. This way you can render the subtitles yourself.",
"href": "captioning/events.html",
"image": "lib/img/sintel-1.jpg",
"labels": [
"VoD",
"External caption",
"Video",
"Audio",
"Events"
]
}
]
},
Expand Down
4 changes: 4 additions & 0 deletions src/core/Settings.js
Expand Up @@ -142,6 +142,7 @@ import Events from './events/Events';
* },
* text: {
* defaultEnabled: true,
* dispatchForManualRendering: false,
* extendSegmentedCues: true,
* imsc: {
* displayForcedOnlyMode: false,
Expand Down Expand Up @@ -482,6 +483,8 @@ import Events from './events/Events';
* @typedef {Object} Text
* @property {boolean} [defaultEnabled=true]
* Enable/disable subtitle rendering by default.
* @property {boolean} [dispatchForManualRendering=false]
* Enable/disable firing of CueEnter/CueExt events. This will disable the display of subtitles and should be used when you want to have full control about rendering them.
* @property {boolean} [extendSegmentedCues=true]
* Enable/disable patching of segmented cues in order to merge as a single cue by extending cue end time.
* @property {boolean} [imsc.displayForcedOnlyMode=false]
Expand Down Expand Up @@ -951,6 +954,7 @@ function Settings() {
},
text: {
defaultEnabled: true,
dispatchForManualRendering: false,
extendSegmentedCues: true,
imsc: {
displayForcedOnlyMode: false,
Expand Down
12 changes: 12 additions & 0 deletions src/streaming/MediaPlayerEvents.js
Expand Up @@ -259,6 +259,18 @@ class MediaPlayerEvents extends EventsBase {
*/
this.TEXT_TRACK_ADDED = 'textTrackAdded';

/**
* Triggered when a text track should be shown
* @event MediaPlayerEvents#CUE_ENTER
*/
this.CUE_ENTER = 'cueEnter'

/**
* Triggered when a text track should be hidden
* @event MediaPlayerEvents#CUE_ENTER
*/
this.CUE_EXIT = 'cueExit'

/**
* Triggered when a throughput measurement based on the last segment request has been stored
* @event MediaPlayerEvents#THROUGHPUT_MEASUREMENT_STORED
Expand Down
13 changes: 7 additions & 6 deletions src/streaming/text/TextController.js
Expand Up @@ -181,7 +181,7 @@ function TextController(config) {

/**
* Event that is triggered if a font download of a font described in an essential property descriptor
* tag fails.
* tag fails.
* @param {FontInfo} font - font information
* @private
*/
Expand All @@ -194,9 +194,9 @@ function TextController(config) {
};

/**
* Set a font with an essential property
* Set a font with an essential property
* @private
*/
*/
function _onFontDownloadSuccess(font) {
logger.debug(`Successfully downloaded ${font.isEssential ? 'an essential' : 'a'} font - fontFamily: ${font.fontFamily}, url: ${font.url}`);
if (font.isEssential) {
Expand Down Expand Up @@ -353,13 +353,14 @@ function TextController(config) {
if (currentNativeTrackInfo && (currentNativeTrackInfo.mode !== Constants.TEXT_DISABLED)) {
textTracks[streamId].setModeForTrackIdx(oldTrackIdx, Constants.TEXT_HIDDEN);
}

textTracks[streamId].setCurrentTrackIdx(idx);

currentTrackInfo = textTracks[streamId].getCurrentTrackInfo();
currentNativeTrackInfo = (currentTrackInfo) ? videoModel.getTextTrack(currentTrackInfo.kind, currentTrackInfo.id, currentTrackInfo.lang, currentTrackInfo.isTTML, currentTrackInfo.isEmbedded) : null;

if (currentTrackInfo && (currentTrackInfo.mode !== Constants.TEXT_DISABLED)) {
const dispatchForManualRendering = settings.get().streaming.text.dispatchForManualRendering;

if (currentTrackInfo && !dispatchForManualRendering && (currentTrackInfo.mode !== Constants.TEXT_DISABLED)) {
textTracks[streamId].setModeForTrackIdx(idx, Constants.TEXT_SHOWING);
}

Expand Down

0 comments on commit db643ad

Please sign in to comment.