Skip to content

Commit

Permalink
feat: add currentPdt getter and getStartDate() method (#661)
Browse files Browse the repository at this point in the history
  • Loading branch information
gkatsev authored Apr 14, 2023
1 parent db4cc9f commit 530170b
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 0 deletions.
10 changes: 10 additions & 0 deletions packages/mux-audio/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
toMuxVideoURL,
Metadata,
MediaError,
getStartDate,
getCurrentPdt,
} from '@mux/playback-core';
import type { PlaybackCore, PlaybackEngine, ExtensionMimeTypeMap } from '@mux/playback-core';
import { getPlayerVersion } from './env';
Expand Down Expand Up @@ -231,6 +233,14 @@ class MuxAudioElement extends CustomAudioElement<HTMLAudioElement> implements Pa
}
}

getStartDate() {
return getStartDate(this.nativeEl, this._hls);
}

get currentPdt() {
return getCurrentPdt(this.nativeEl, this._hls);
}

get preferPlayback(): ValueOf<PlaybackTypes> | undefined {
const val = this.getAttribute(Attributes.PREFER_PLAYBACK);
if (val === PlaybackTypes.MSE || val === PlaybackTypes.NATIVE) return val;
Expand Down
27 changes: 27 additions & 0 deletions packages/mux-audio/test/player.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,31 @@ describe('<mux-audio>', () => {
assert.equal(player.metadata.sub_property_id, 'sub-id-12');
assert.equal(player.metadata.video_id, playbackId);
});

it('currentPdt and getStartDate work as expected', async function () {
this.timeout(5000);

const player = await fixture(`<mux-audio
src="https://stream.mux.com/UgKrPYAnjMjP6oMF4Kcs1gWVhtgYDR02EHQGnj022X1Xo.m3u8"
env-key="ilc02s65tkrc2mk69b7q2qdkf"
prefer-playback="mse"
muted
preload="auto"
></mux-player>`);

await aTimeout(1000);

player.currentTime = 60;

await aTimeout(50);

const currentPdt = player.currentPdt;
const startDate = player.getStartDate();

assert.equal(
startDate.getTime(),
currentPdt.getTime() - player.currentTime * 1000,
'currentPdt should be ~60 seconds greater than getStartDate'
);
});
});
3 changes: 3 additions & 0 deletions packages/mux-player/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
| `play()` | Identical to the [native `play()` method](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play). |
| `pause()` | Identical to the [native `pause()` method](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause). |
| `addCuePoints()` | Add an array of metadata CuePoints of "shape" `{ time: number; value: any; }` to the Mux Player instance for the current media |
| `getStartDate()` | Will return a Date that matches the earliest PDT in your stream. Identical to [native `getStartDate()` method](https://html.spec.whatwg.org/multipage/media.html#dom-media-getstartdate), if exists. |
|

<!-- UNDOCUMENTED
| `addTextTrack()` | Identical to the [native `addTextTrack()` method](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#methods). |
Expand All @@ -77,6 +79,7 @@
| `videoWidth` <sub><sup>Read only</sup></sub> | `number` | Identical to the [native `videoWidth` property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoWidth) | `0` |
| `videoHeight` <sub><sup>Read only</sup></sub> | `number` | Identical to the [native `videoHeight` property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/videoHeight) | `0` |
| `currentTime` | `number` | Identical to the [native `currentTime` property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/currentTime) | `0` |
| `currentPdt` | `Date` | Will return a Date that matches current PDT in your stream. Accounting for currentTime. | `0` |
| `volume` | `number` | Identical to the [native `volume` property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/volume) | `1` |
| `poster` | `string` (URL) | Identical to the [native `poster` property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement#properties). Will use the automatically generated poster based on your `playback-id` by default. Remove the poster by setting the value to an empty string. | Derived |
| `storyboard` | `string` (URL) | URL for the [Mux Storyboard](https://docs.mux.com/guides/video/create-timeline-hover-previews). Will automatically generate the url based on your `playback-id` and token. Will not be set if the provided storyboardToken is invalid. | Derived |
Expand Down
8 changes: 8 additions & 0 deletions packages/mux-player/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,14 @@ class MuxPlayerElement extends VideoApiElement implements MuxPlayerElement {
return this.media?.cuePoints ?? [];
}

getStartDate() {
return this.media?.getStartDate();
}

get currentPdt() {
return this.media?.currentPdt;
}

/**
* Get the signing tokens for the Mux asset URL's.
*/
Expand Down
34 changes: 34 additions & 0 deletions packages/mux-player/test/player.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1266,3 +1266,37 @@ describe.skip('Feature: cuePoints', async () => {
assert.equal(muxPlayerEl.cuePoints.length, 0, 'cuePoints should be empty');
});
});

describe('currentPdt and getStartDate', async () => {
it('currentPdt and getStartDate work as expected', async function () {
this.timeout(5000);

const player = await fixture(`<mux-player
env-key="ilc02s65tkrc2mk69b7q2qdkf"
stream-type="on-demand"
prefer-playback="mse"
muted
title="A title"
preload="auto"
></mux-player>`);

player.addEventListener('loadstart', async function () {
player.currentTime = 60;

await aTimeout(50);

const currentPdt = player.currentPdt;
const startDate = player.getStartDate();

assert.equal(
startDate.getTime(),
currentPdt.getTime() - player.currentTime * 1000,
'currentPdt should be 60 seconds greater than getStartDate'
);
});

await aTimeout(50);

player.playbackId = 'UgKrPYAnjMjP6oMF4Kcs1gWVhtgYDR02EHQGnj022X1Xo';
});
});
10 changes: 10 additions & 0 deletions packages/mux-video/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
addCuePoints,
getCuePoints,
getActiveCuePoint,
getStartDate,
getCurrentPdt,
getStreamType,
getTargetLiveWindow,
getLiveEdgeStart,
Expand Down Expand Up @@ -424,6 +426,14 @@ class MuxVideoElement extends CustomVideoElement<HTMLVideoElement> implements Pa
return getCuePoints(this.nativeEl);
}

getStartDate() {
return getStartDate(this.nativeEl, this._hls);
}

get currentPdt() {
return getCurrentPdt(this.nativeEl, this._hls);
}

get preferPlayback(): ValueOf<PlaybackTypes> | undefined {
const val = this.getAttribute(Attributes.PREFER_PLAYBACK);
if (val === PlaybackTypes.MSE || val === PlaybackTypes.NATIVE) return val;
Expand Down
27 changes: 27 additions & 0 deletions packages/mux-video/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,33 @@ describe('<mux-video>', () => {
assert.equal(player.metadata.video_id, playbackId);
});

it('currentPdt and getStartDate work as expected', async function () {
this.timeout(5000);

const player = await fixture(`<mux-video
src="https://stream.mux.com/UgKrPYAnjMjP6oMF4Kcs1gWVhtgYDR02EHQGnj022X1Xo.m3u8"
env-key="ilc02s65tkrc2mk69b7q2qdkf"
prefer-playback="mse"
muted
preload="auto"
></mux-player>`);

await aTimeout(1000);

player.currentTime = 60;

await aTimeout(50);

const currentPdt = player.currentPdt;
const startDate = player.getStartDate();

assert.equal(
startDate.getTime(),
currentPdt.getTime() - player.currentTime * 1000,
'currentPdt should be ~60 seconds greater than getStartDate'
);
});

describe('Feature: cuePoints', async () => {
it('adds cuepoints', async () => {
const cuePoints = [
Expand Down
3 changes: 3 additions & 0 deletions packages/playback-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
setupCuePoints,
getCuePointsTrack,
} from './tracks';
import { getStartDate, getCurrentPdt } from './pdt';
import {
inSeekableRange,
toPlaybackIdParts,
Expand Down Expand Up @@ -46,6 +47,8 @@ export {
getActiveCuePoint,
getCuePointsTrack,
setupCuePoints,
getStartDate,
getCurrentPdt,
};
export * from './types';

Expand Down
37 changes: 37 additions & 0 deletions packages/playback-core/src/pdt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { PlaybackEngine } from './types';

type MediaWithPDT = HTMLMediaElement & { getStartDate?: () => Date };

export function getStartDate(mediaEl: MediaWithPDT, hls: PlaybackEngine | undefined) {
if (hls) {
const playingDate = hls.playingDate;

if (playingDate != null) {
// If the video is very long and the currentTime will transition day boundaries,
// this may end up not being accurate
return new Date(playingDate.getTime() - mediaEl.currentTime * 1000);
}
}

if (typeof mediaEl.getStartDate === 'function') {
return mediaEl.getStartDate();
}

return new Date(NaN);
}

export function getCurrentPdt(mediaEl: MediaWithPDT, hls: PlaybackEngine | undefined) {
if (hls && hls.playingDate) {
return hls.playingDate;
}

if (typeof mediaEl.getStartDate === 'function') {
const startDate = mediaEl.getStartDate();

// If the video is very long and the currentTime will transition day boundaries,
// this may end up not being accurate
return new Date(startDate.getTime() + mediaEl.currentTime * 1000);
}

return new Date(NaN);
}
145 changes: 145 additions & 0 deletions packages/playback-core/test/pdt.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { assert, aTimeout } from '@open-wc/testing';
import { getStartDate, getCurrentPdt } from '../src/pdt';

describe('getCurrentPdt', function () {
describe('with hls.js', function () {
it('will return playingDate, if not null', function () {
const time = Date.now();
const currentPdt = getCurrentPdt(
{},
{
playingDate: new Date(time),
}
);

assert.equal(currentPdt.getTime(), time, 'the returned date matches the expected date');
});

it('will return an invalid date if playingDate is null', function () {
const currentPdt = getCurrentPdt(
{},
{
playingDate: null,
}
);

assert.isNaN(currentPdt.getTime(), "the currentPdt's getTime() is NaN, which is an invalid time");
});
});

describe('with native video', function () {
it('will return invalid date if no getStartDate method', function () {
const currentPdt = getCurrentPdt({}, {});

assert.isNaN(currentPdt.getTime(), "the currentPdt's getTime() is NaN, which is an invalid time");
});

it('will return getStartDate plus currentTime', function () {
const time = Date.now();
let currentPdt = getCurrentPdt(
{
currentTime: 0,
getStartDate() {
return new Date(time);
},
},
{}
);

assert.equal(
currentPdt.getTime(),
time,
'with a currentTime of 0, getCurrentPdt is equivalent to getStartDate time'
);

currentPdt = getCurrentPdt(
{
currentTime: 60,
getStartDate() {
return new Date(time);
},
},
{}
);

assert.equal(
currentPdt.getTime(),
time + 60 * 1000,
'with a currentTime of 0, getCurrentPdt is equivalent to getStartDate time plus 60 seconds'
);
});
});
});

describe('getStartDate', function () {
describe('with hls.js', function () {
it('will return a Date that is currentTime before playingDate', function () {
const time = Date.now();
let startDate = getStartDate(
{
currentTime: 0,
},
{
playingDate: new Date(time),
}
);

assert.equal(
startDate.getTime(),
time,
'with a currentTime of zero, the returned date is equivalent to playingDate'
);

startDate = getStartDate(
{
currentTime: 60,
},
{
playingDate: new Date(time),
}
);

assert.equal(startDate.getTime(), time - 60 * 1000, 'the returned date should be 60 seconds before playingDate');
});

it('will return an invalid Date if playingDate is null', function () {
const startDate = getStartDate(
{
currentTime: 60,
},
{
playingDate: null,
}
);

assert.isNaN(startDate.getTime(), 'NaN is an invalid date, when playingDate is null');
});
});

describe('with native video', function () {
it('will return invalid date if no getStartDate method', function () {
const currentPdt = getCurrentPdt({}, {});

assert.isNaN(currentPdt.getTime(), "the currentPdt's getTime() is NaN, which is an invalid time");
});

it('will return getStartDate', function () {
const time = Date.now();
const startDate = getStartDate(
{
currentTime: 60,
getStartDate() {
return new Date(time);
},
},
{}
);

assert.equal(
startDate.getTime(),
time,
'getCurrentPdt is equivalent to getStartDate time, regardless of currentTime'
);
});
});
});

5 comments on commit 530170b

@vercel
Copy link

@vercel vercel bot commented on 530170b Apr 14, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

elements-demo-create-react-app – ./examples/create-react-app-with-typescript

elements-demo-create-react-app.vercel.app
elements-demo-create-react-app-git-main-mux.vercel.app
elements-demo-create-react-app-mux.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 530170b Apr 14, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

elements-demo-svelte-kit – ./examples/svelte-kit

elements-demo-svelte-kit.vercel.app
elements-demo-svelte-kit-git-main-mux.vercel.app
elements-demo-svelte-kit-mux.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 530170b Apr 14, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

elements-demo-vanilla – ./examples/vanilla-ts-esm

elements-demo-vanilla-git-main-mux.vercel.app
elements-demo-vanilla-mux.vercel.app
elements-demo-vanilla.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 530170b Apr 14, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

elements-demo-vue – ./examples/vue-with-typescript

elements-demo-vue.vercel.app
elements-demo-vue-mux.vercel.app
elements-demo-vue-git-main-mux.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 530170b Apr 14, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

elements-demo-nextjs – ./examples/nextjs-with-typescript

elements-demo-nextjs-mux.vercel.app
elements-demo-nextjs.vercel.app
elements-demo-nextjs-git-main-mux.vercel.app

Please sign in to comment.