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: CMCD v2 LTC and MSD keys #7412

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Peter Nycander <[email protected]>
Philo Inc. <*@philo.com>
PikachuEXE <[email protected]>
Prakash <[email protected]>
Qualabs <*@qualabs.com>
Robert Colantuoni <[email protected]>
Robert Galluccio <[email protected]>
Rodolphe Breton <[email protected]>
Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ Jeffrey Swan <[email protected]>
Jesper Haug Karsrud <[email protected]>
Jesse Gunsch <[email protected]>
Jimmy Ly <[email protected]>
Joaquin Bartaburu <[email protected]>
Joey Parrish <[email protected]>
Johan Sundström <[email protected]>
John Bowers <[email protected]>
Jonas Birmé <[email protected]>
Jono Ward <[email protected]>
Jozef Chúťka <[email protected]>
Juan Manuel Tomás <[email protected]>
Julian Domingo <[email protected]>
Jun Hong Chong <[email protected]>
Jürgen Kartnaller <[email protected]>
Expand Down Expand Up @@ -128,6 +130,7 @@ Sander Saares <[email protected]>
Sandra Lokshina <[email protected]>
Sanil Raut <[email protected]>
Satheesh Velmurugan <[email protected]>
Sebastián Piquerez <[email protected]>
Semih Gokceoglu <[email protected]>
Seongryun Jo <[email protected]>
Seth Madison <[email protected]>
Expand Down
1 change: 1 addition & 0 deletions demo/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ shakaDemo.Config = class {
.addBoolInput_('Enabled', 'cmcd.enabled')
.addTextInput_('Session ID', 'cmcd.sessionId')
.addTextInput_('Content ID', 'cmcd.contentId')
.addTextInput_('Version', 'cmcd.reporting.requestMode.version')
.addNumberInput_('RTP safety Factor', 'cmcd.rtpSafetyFactor',
/* canBeDecimal= */ true)
.addBoolInput_('Use Headers', 'cmcd.useHeaders');
Expand Down
17 changes: 16 additions & 1 deletion externs/cmcd.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
* st: (string|undefined),
* v: (number|undefined),
* bs: (boolean|undefined),
* rtp: (number|undefined)
* rtp: (number|undefined),
* msd: (number|undefined),
* ltc: (number|undefined),
* }}
*
* @description
Expand Down Expand Up @@ -182,5 +184,18 @@
* delivery. The concept is that each client receives the throughput necessary
* for great performance, but no more. The CDN may not support the rtp
* feature.
*
* @property {number} msd
* The Media Start Delay represents in milliseconds the delay between the
* initiation of the playback request and the moment the first frame is
* rendered. This is sent only once when it is calculated.
*
* @property {number} ltc
* Live Stream Latency
*
* The time delta between when a given media timestamp was made available at
* the origin and when it was rendered by the client. The accuracy of this
* estimate is dependent on synchronization between the packager and the
* player clocks.
*/
var CmcdData;
38 changes: 37 additions & 1 deletion externs/shaka/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -2082,6 +2082,39 @@ shaka.extern.AbrConfiguration;
*/
shaka.extern.AdvancedAbrConfiguration;

/**
* @typedef {{
* version: number
* }}
*
* @description
* Configuration options for CMCD Request Mode reporting.
*
* @property {number} version
* The CMCD version to use for Request Mode reporting.
* Valid values are <code>1</code> or <code>2</code>, corresponding to CMCD v1
* and CMCD v2 specifications, respectively.
* <br>
* Defaults to <code>2</code>.
*/
shaka.extern.RequestMode;

/**
* @typedef {{
* requestMode: shaka.extern.RequestMode
* }}
*
* @description
* Configuration for CMCD reporting modes.
*
* @property {shaka.extern.RequestMode} requestMode
* Configuration options for Request Mode reporting.
* <br>
* Request Mode attaches CMCD data to each media object request, matching the
* default delivery mode in CMCD v1. It is mandatory for CMCD clients to
* support this mode.
*/
shaka.extern.Reporting;

/**
* @typedef {{
Expand All @@ -2090,7 +2123,8 @@ shaka.extern.AdvancedAbrConfiguration;
* sessionId: string,
* contentId: string,
* rtpSafetyFactor: number,
* includeKeys: !Array<string>
* includeKeys: !Array<string>,
* reporting: shaka.extern.Reporting
* }}
*
* @description
Expand Down Expand Up @@ -2129,6 +2163,8 @@ shaka.extern.AdvancedAbrConfiguration;
* will be included.
* <br>
* Defaults to <code>[]</code>.
* @property {shaka.extern.Reporting} reporting
* Reporting mode configuration.
* @exportDoc
*/
shaka.extern.CmcdConfiguration;
Expand Down
36 changes: 32 additions & 4 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -2466,6 +2466,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
movePlayhead: (delta) => { mediaElement.currentTime += delta; },
});

if (this.cmcdManager_) {
const onPlaybackPlay = () => this.cmcdManager_.onPlaybackPlay();
const onPlaybackPlaying = () => this.cmcdManager_.onPlaybackPlaying();

this.loadEventManager_.listenOnce(
mediaElement, 'play', onPlaybackPlay);
this.loadEventManager_.listenOnce(
mediaElement, 'playing', onPlaybackPlaying);
}

const updateStateHistory = () => this.updateStateHistory_();
const onRateChange = () => this.onRateChange_();
this.loadEventManager_.listen(
Expand Down Expand Up @@ -3713,6 +3723,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
getNetworkingEngine: () => this.getNetworkingEngine(),
getVariantTracks: () => this.getVariantTracks(),
isLive: () => this.isLive(),
getLiveLatency: () => this.getLiveLatency(),
};

return new shaka.util.CmcdManager(playerInterface, this.config_.cmcd);
Expand Down Expand Up @@ -5647,6 +5658,25 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
return info;
}

/**
* Get latency in milliseconds between the live edge and what's currently
* playing.
*
* @return {?number} The latency in milliseconds, or null if nothing
* is playing.
*/
getLiveLatency() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add unit test for this method.

const element = this.video_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check does video element exists.


if (!element) {
return null;
}

const now = this.getPresentationStartTimeAsDate().getTime() +
element.currentTime * 1000;
return Date.now() - now;
}

/**
* Get statistics for the current playback session. If the player is not
* playing content, this will return an empty stats object.
Expand Down Expand Up @@ -5716,10 +5746,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
}

if (this.isLive()) {
const now = this.getPresentationStartTimeAsDate().valueOf() +
element.currentTime * 1000;
const latency = (Date.now() - now) / 1000;
this.stats_.setLiveLatency(latency);
const latency = this.getLiveLatency() || 0;
this.stats_.setLiveLatency(latency / 1000);
}

if (this.manifest_) {
Expand Down
76 changes: 65 additions & 11 deletions lib/util/cmcd_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ shaka.util.CmcdManager = class {
* @private {boolean}
*/
this.lowLatency_ = false;

/**
* @private {number|undefined}
*/
this.playbackPlayTime_ = undefined;

/**
* @private {number|undefined}
*/
this.playbackPlayingTime_ = undefined;

/**
* @private {boolean}
*/
this.msdSent_ = false;
}

/**
Expand Down Expand Up @@ -192,6 +207,24 @@ shaka.util.CmcdManager = class {
}
}

/**
* Apply CMCD data to a manifest request.
*/
onPlaybackPlay() {
if (!this.playbackPlayTime_) {
this.playbackPlayTime_ = Date.now();
}
}

/**
* Apply CMCD data to a manifest request.
*/
onPlaybackPlaying() {
if (!this.playbackPlayingTime_) {
this.playbackPlayingTime_ = Date.now();
}
}

/**
* Apply CMCD data to a segment request
*
Expand Down Expand Up @@ -371,7 +404,7 @@ shaka.util.CmcdManager = class {
this.config_.sessionId = window.crypto.randomUUID();
}
return {
v: shaka.util.CmcdManager.Version,
v: this.config_.reporting.requestMode.version,
sf: this.sf_,
sid: this.config_.sessionId,
cid: this.config_.contentId,
Expand Down Expand Up @@ -410,6 +443,23 @@ shaka.util.CmcdManager = class {
data.su = this.buffering_;
}

if (
data.v === shaka.util.CmcdManager.Version.VERSION_2 &&
this.playerInterface_.isLive()
) {
data.ltc = this.playerInterface_.getLiveLatency();
}

if (
data.v === shaka.util.CmcdManager.Version.VERSION_2 &&
!this.msdSent_ &&
this.playbackPlayingTime_ &&
this.playbackPlayTime_
) {
data.msd = this.playbackPlayingTime_ - this.playbackPlayTime_;
this.msdSent_ = true;
}

const output = this.filterKeys_(data);

if (useHeaders) {
Expand Down Expand Up @@ -810,8 +860,8 @@ shaka.util.CmcdManager = class {
const headerGroups = [{}, {}, {}, {}];
const headerMap = {
br: 0, d: 0, ot: 0, tb: 0,
bl: 1, dl: 1, mtp: 1, nor: 1, nrr: 1, su: 1,
cid: 2, pr: 2, sf: 2, sid: 2, st: 2, v: 2,
bl: 1, dl: 1, mtp: 1, nor: 1, nrr: 1, su: 1, ltc: 1,
cid: 2, pr: 2, sf: 2, sid: 2, st: 2, v: 2, msd: 2,
bs: 3, rtp: 3,
};

Expand Down Expand Up @@ -873,7 +923,8 @@ shaka.util.CmcdManager = class {
* getCurrentTime: function():number,
* getPlaybackRate: function():number,
* getVariantTracks: function():Array.<shaka.extern.Track>,
* isLive: function():boolean
* isLive: function():boolean,
* getLiveLatency: function():number
* }}
*
* @property {function():number} getBandwidthEstimate
Expand All @@ -888,6 +939,9 @@ shaka.util.CmcdManager = class {
* Get the variant tracks
* @property {function():boolean} isLive
* Get if the player is playing live content.
* @property {function():number} getLiveLatency
* Get latency in milliseconds between the live edge and what's currently
* playing.
*/
shaka.util.CmcdManager.PlayerInterface;

Expand All @@ -907,6 +961,13 @@ shaka.util.CmcdManager.ObjectType = {
OTHER: 'o',
};

/**
* @enum {number}
*/
shaka.util.CmcdManager.Version = {
VERSION_1: 1,
VERSION_2: 2,
};

/**
* @enum {string}
Expand All @@ -929,10 +990,3 @@ shaka.util.CmcdManager.StreamingFormat = {
SMOOTH: 's',
OTHER: 'o',
};


/**
* The CMCD spec version
* @const {number}
*/
shaka.util.CmcdManager.Version = 1;
5 changes: 5 additions & 0 deletions lib/util/player_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,11 @@ shaka.util.PlayerConfiguration = class {
rtpSafetyFactor: 5,
useHeaders: false,
includeKeys: [],
reporting: {
requestMode: {
version: 1,
},
},
};

const cmsd = {
Expand Down
1 change: 1 addition & 0 deletions test/cast/cast_utils_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('CastUtils', () => {
'getNonDefaultConfiguration',
'addFont',
'getFetchedPlaybackInfo',
'getLiveLatency',

// Test helper methods (not @export'd)
'createDrmEngine',
Expand Down
39 changes: 39 additions & 0 deletions test/player_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2876,6 +2876,45 @@ describe('Player', () => {
}
}); // describe('languages')

describe('getLiveLatency()', () => {
let timeline;

beforeEach(async () => {
// Create a presentation timeline for a live stream.
timeline = new shaka.media.PresentationTimeline(300, 0);
timeline.setStatic(false); // Indicate that this is a live stream.

// Set an initial program date time to simulate a live stream with a
// known start time.
timeline.setInitialProgramDateTime(1000);

// Generate a manifest that uses this timeline.
manifest = shaka.test.ManifestGenerator.generate((manifest) => {
manifest.presentationTimeline = timeline;
manifest.addVariant(0, (variant) => {
variant.addVideo(1);
});
});

// Load the player with the live manifest.
await player.load(fakeManifestUri, null, fakeMimeType);

video.currentTime = 10; // Simulate that we're 10 seconds into playback.
});

it('returns null if video element does not exist', async () => {
await player.detach();
const latency = player.getLiveLatency();
expect(latency).toBeNull();
});

it('returns correct latency when video is playing', () => {
Date.now = () => 2000 * 1000;
const latency = player.getLiveLatency();
expect(latency).toBe(990000);
});
});

describe('getStats', () => {
const oldDateNow = Date.now;

Expand Down
Loading
Loading