Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add event stream support #1382

Merged
merged 18 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion src/dash-playlist-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import containerRequest from './util/container-request.js';
import {toUint8} from '@videojs/vhs-utils/es/byte-helpers';
import logger from './util/logger';
import {merge} from './util/vjs-compat';
import { addMetadata, createMetadataTrackIfNotExists } from './util/text-tracks';

const { EventTarget } = videojs;

Expand Down Expand Up @@ -300,7 +301,7 @@ export default class DashPlaylistLoader extends EventTarget {
// DashPlaylistLoader must accept either a src url or a playlist because subsequent
// playlist loader setups from media groups will expect to be able to pass a playlist
// (since there aren't external URLs to media playlists with DASH)
constructor(srcUrlOrPlaylist, vhs, options = { }, mainPlaylistLoader) {
constructor(srcUrlOrPlaylist, vhs, options = { }, mainPlaylistLoader, inbandTextTracks, tech) {
super();

this.mainPlaylistLoader_ = mainPlaylistLoader || this;
Expand All @@ -312,6 +313,8 @@ export default class DashPlaylistLoader extends EventTarget {

this.vhs_ = vhs;
this.withCredentials = withCredentials;
this.inbandTextTracks_ = inbandTextTracks;
this.tech_ = tech;

if (!srcUrlOrPlaylist) {
throw new Error('A non-empty playlist URL or object is required');
Expand Down Expand Up @@ -773,6 +776,8 @@ export default class DashPlaylistLoader extends EventTarget {
this.updateMinimumUpdatePeriodTimeout_();
}

this.addEventStreamToMetadataTrack_(newMain);

return Boolean(newMain);
}

Expand Down Expand Up @@ -901,4 +906,31 @@ export default class DashPlaylistLoader extends EventTarget {

this.trigger('loadedplaylist');
}

/**
* Takes eventstream data from a parsed DASH manifest and adds it to the metadata text track.
*
* @param {manifest} newMain the newly parsed manifest
*/
addEventStreamToMetadataTrack_(newMain) {
// Only add new event stream metadata if we have a new manifest.
if (newMain && this.mainPlaylistLoader_.main.eventStream) {
createMetadataTrackIfNotExists(this.inbandTextTracks_, 'EventStream', this.tech_);

// convert EventStream to ID3-like data.
this.mainPlaylistLoader_.main.eventStream.forEach((eventStreamNode) => {
const metadataArray = [{
cueTime: eventStreamNode.start,
frames: [{ data: eventStreamNode.messageData }]
}];

addMetadata({
inbandTextTracks: this.inbandTextTracks_,
metadataArray,
timestampOffset: eventStreamNode.presentationTimeOffset,
videoDuration: this.mainPlaylistLoader_.main.duration
});
});
}
}
adrums86 marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion src/playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ export class PlaylistController extends videojs.EventTarget {
// manifest object (instead of a URL). In the case of vhs-json, the default
// PlaylistLoader should be used.
this.mainPlaylistLoader_ = this.sourceType_ === 'dash' ?
new DashPlaylistLoader(src, this.vhs_, this.requestOptions_) :
new DashPlaylistLoader(src, this.vhs_, this.requestOptions_, undefined, this.inbandTextTracks_, this.tech_) :
new PlaylistLoader(src, this.vhs_, this.requestOptions_);
this.setupMainPlaylistLoaderListeners_();

Expand Down
81 changes: 80 additions & 1 deletion test/dash-playlist-loader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import {generateSidxKey} from 'mpd-parser';
import {
useFakeEnvironment,
standardXHRResponse,
urlTo
urlTo,
createPlayer
} from './test-helpers';
// needed for plugin registration
import '../src/videojs-http-streaming';
import testDataManifests from 'create-test-data!manifests';
import { sidx as sidxResponse } from 'create-test-data!segments';
import {mp4VideoInit as mp4VideoInitSegment} from 'create-test-data!segments';
import { merge } from '../src/util/vjs-compat';

QUnit.module('DASH Playlist Loader: unit', {
beforeEach(assert) {
Expand Down Expand Up @@ -2988,3 +2990,80 @@ QUnit.test('updateMain: updates playlists and mediaGroups when labels change', f
'updates playlists and media groups'
);
});

QUnit.test('addEventStreamToMetadataTrack_ adds EventStream data to the metadata text track', function(assert) {
const player = createPlayer(merge({}, this.playerOptions));
const inbandTextTracks = {};
const loader = new DashPlaylistLoader('eventStreamMessageData.mpd', this.fakeVhs, undefined, undefined, inbandTextTracks, player.tech_);
const expectedCueValues = [
{
startTime: 63857834.256000005,
data: 'google_7617584398642699833'
},
{
startTime: 63857835.056,
data: 'google_gkmxVFMIdHz413g3pIgZtITUSFFQYDnQ421MGEkVnTA'
},
{
startTime: 63857836.056,
data: 'google_Yl7LFi1Fh-TD39nqQzIiGLDD1lx7tYRjjmYND7tEEjM'
},
{
startTime: 63857836.650000006,
data: 'google_5437877779805246002'
},
{
startTime: 63857837.056,
data: 'google_8X2eBAFbC2cUJmNNHkrcDKqSJQncj2nrVoB2eIu6lrc'
},
{
startTime: 63857838.056,
data: 'google_Qyxg2ZhKfBUls-J7oj0Re0_-gCQFviaaEMMDvIOTEWE'
},
{
startTime: 63857838.894,
data: 'google_7174574530630198647'
},
{
startTime: 63857839.056,
data: 'google_EFt2jovkcT9PqjuLLC5kH7gIIjWvc0iIhROFED6kqsg'
},
{
startTime: 63857840.056,
data: 'google_eUHx4vMmAikHojJZLOTR2XZdg1A9b9A8TY7F2CVC3cA'
},
{
startTime: 63857841.056,
data: 'google_gkmxVFMIdHz413g3pIgZtITUSFFQYDnQ421MGEkVnTA'
},
{
startTime: 63857841.638000004,
data: 'google_1443613685977331553'
},
{
startTime: 63857842.056,
data: 'google_Yl7LFi1Fh-TD39nqQzIiGLDD1lx7tYRjjmYND7tEEjM'
},
{
startTime: 63857843.056,
data: 'google_8X2eBAFbC2cUJmNNHkrcDKqSJQncj2nrVoB2eIu6lrc'
},
{
startTime: 63857843.13200001,
data: 'google_5822903356700578162'
}
];

// Load EventStream manifest
loader.mainXml_ = testDataManifests.eventStreamMessageData;
loader.handleMain_();
// Map actual values to expected value objects
const actualCueValues = loader.mainPlaylistLoader_.inbandTextTracks_.metadataTrack_.cues_.map((cue) => {
return {
startTime: cue.startTime,
data: cue.value.data
};
});

assert.deepEqual(actualCueValues, expectedCueValues, 'dash EventStream is added to metadata text track');
});
49 changes: 49 additions & 0 deletions test/manifests/eventStreamMessageData.mpd
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:_XMLSchema-instance="http://www.w3.org/2001/XMLSchema-instance" _XMLSchema-instance:schemaLocation="urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd" id="201" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="dynamic" availabilityStartTime="2021-02-25T22:43:33.112Z" publishTime="2023-03-07T01:01:34.248221397Z" minimumUpdatePeriod="PT5S" minBufferTime="PT30S" timeShiftBufferDepth="PT1M0S" suggestedPresentationDelay="PT25S">
<BaseURL>https://547f72e6652371c3.mediapackage.us-east-1.amazonaws.com/out/v1/ef88d1a64fd543f4a4be9a56c913d868/</BaseURL>
<Location>https://dai.google.com/linear/dash/pa/event/PSzZMzAkSXCmlJOWDmRj8Q/stream/7e6b742f-0013-412c-b8a9-fd527adafc89:ATL/manifest.mpd</Location>
<Period id="dai_pod-0001065804-ad-1" start="PT17738H17M14.156S" duration="PT9.977S">
<BaseURL>https://dai.google.com/linear/pods/v1/p/PSzZMzAkSXCmlJOWDmRj8Q/7e6b742f-0013-412c-b8a9-fd527adafc89:ATL/1065804/0/0/</BaseURL>
<SegmentTemplate media="$RepresentationID$/$Number$.mp4" initialization="$RepresentationID$/init.mp4"/>
<EventStream schemeIdUri="urn:google:dai:2018" timescale="1000">
<Event presentationTime="100" duration="0" id="0" messageData="google_7617584398642699833"/>
<Event presentationTime="900" duration="0" id="5" messageData="google_gkmxVFMIdHz413g3pIgZtITUSFFQYDnQ421MGEkVnTA"/>
<Event presentationTime="1900" duration="0" id="6" messageData="google_Yl7LFi1Fh-TD39nqQzIiGLDD1lx7tYRjjmYND7tEEjM"/>
<Event presentationTime="2494" duration="0" id="1" messageData="google_5437877779805246002"/>
<Event presentationTime="2900" duration="0" id="7" messageData="google_8X2eBAFbC2cUJmNNHkrcDKqSJQncj2nrVoB2eIu6lrc"/>
<Event presentationTime="3900" duration="0" id="8" messageData="google_Qyxg2ZhKfBUls-J7oj0Re0_-gCQFviaaEMMDvIOTEWE"/>
<Event presentationTime="4738" duration="0" id="2" messageData="google_7174574530630198647"/>
<Event presentationTime="4900" duration="0" id="9" messageData="google_EFt2jovkcT9PqjuLLC5kH7gIIjWvc0iIhROFED6kqsg"/>
<Event presentationTime="5900" duration="0" id="10" messageData="google_eUHx4vMmAikHojJZLOTR2XZdg1A9b9A8TY7F2CVC3cA"/>
<Event presentationTime="6900" duration="0" id="11" messageData="google_gkmxVFMIdHz413g3pIgZtITUSFFQYDnQ421MGEkVnTA"/>
<Event presentationTime="7482" duration="0" id="3" messageData="google_1443613685977331553"/>
<Event presentationTime="7900" duration="0" id="12" messageData="google_Yl7LFi1Fh-TD39nqQzIiGLDD1lx7tYRjjmYND7tEEjM"/>
<Event presentationTime="8900" duration="0" id="13" messageData="google_8X2eBAFbC2cUJmNNHkrcDKqSJQncj2nrVoB2eIu6lrc"/>
<Event presentationTime="8976" duration="0" id="4" messageData="google_5822903356700578162"/>
</EventStream>
<AdaptationSet id="0" contentType="audio">
<SegmentTemplate timescale="48000" startNumber="0">
<SegmentTimeline>
<S d="96240" r="3"/>
<S d="93936"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation audioSamplingRate="48000" mimeType="audio/mp4" codecs="mp4a.40.2" id="a87ff679a2f3e71d9181a67b7542122c" bandwidth="98000">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
</Representation>
<Representation audioSamplingRate="48000" mimeType="audio/mp4" codecs="mp4a.40.2" id="eccbc87e4b5ce2fe28308fd9f2a7baf3" bandwidth="130000">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
</Representation>
</AdaptationSet>
<AdaptationSet startWithSAP="1" id="1" contentType="video" minFrameRate="24000/1001" maxFrameRate="30000/1001" segmentAlignment="true">
<SegmentTemplate timescale="90000" startNumber="0">
<SegmentTimeline>
<S d="180180" r="3"/>
<S d="177210"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation width="1280" height="720" frameRate="30000/1001" mimeType="video/mp4" codecs="avc1.64001f" id="c4ca4238a0b923820dcc509a6f75849b" bandwidth="331000"/>
<Representation width="480" height="270" frameRate="24000/1001" mimeType="video/mp4" codecs="avc1.42c015" id="c81e728d9d4c2f636f067f89cc14862c" bandwidth="137000"/>
</AdaptationSet>
</Period>
</MPD>