Browse Source

ignore: add stub convertToStreamTime method (#257)

* initial method stub

* take playlist directly instead of reference to MasterPlaylistController. Simplify method arguments.

* rework API to have onsuccess and onreject callbacks. Return accurate and estimate values where appropriate

* use node style callback. Added top-level test for convertToStreamTime

* skip flakey test
pull/286/head
ldayananda 7 years ago
committed by GitHub
parent
commit
de5aacb782
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 88
      src/util/time.js
  2. 9
      src/videojs-http-streaming.js
  3. 196
      test/util/time.test.js
  4. 73
      test/videojs-http-streaming.test.js

88
src/util/time.js

@ -0,0 +1,88 @@
/**
* @file time.js
*/
const findSegmentForTime = (time, playlist) => {
if (!playlist.segments || playlist.segments.length === 0) {
return;
}
// Assumptions:
// - there will always be a segment.duration
// - we can start from zero
// - segments are in time order
// - segment.start and segment.end only come
// from syncController
let manifestTime = 0;
for (let i = 0; i < playlist.segments.length; i++) {
const segment = playlist.segments[i];
const estimatedStart = manifestTime;
const estimatedEnd = manifestTime + segment.duration;
if (segment.start <= time && time <= segment.end) {
return {
segment,
estimatedStart,
estimatedEnd,
type: 'accurate'
};
} else if (estimatedStart <= time && time <= estimatedEnd) {
return {
segment,
estimatedStart,
estimatedEnd,
type: 'estimate'
};
}
manifestTime = estimatedEnd;
}
return null;
};
export const getStreamTime = ({
playlist,
time = undefined,
callback
}) => {
if (!playlist || time === undefined) {
return callback({
message: 'getStreamTime: playlist and time must be provided'
});
} else if (!callback) {
throw new Error('getStreamTime: callback must be provided');
}
const matchedSegment = findSegmentForTime(time, playlist);
if (!matchedSegment) {
return callback({
message: 'valid streamTime was not found'
});
}
if (matchedSegment.type === 'estimate') {
return callback({
message:
'Accurate streamTime could not be determined. Please seek to e.seekTime and try again',
seekTime: matchedSegment.estimatedStart
});
}
const streamTime = {
mediaSeconds: time
};
if (matchedSegment.segment.dateTimeObject) {
// TODO this is currently the time of the beginning of the
// segment. This still needs to be modified to be offset
// by the time requested.
streamTime.programDateTime = matchedSegment.segment.dateTimeObject.toISOString();
}
return callback(null, streamTime);
};

9
src/videojs-http-streaming.js

@ -10,6 +10,7 @@ import Playlist from './playlist';
import xhrFactory from './xhr';
import { Decrypter, AsyncStream, decrypt } from 'aes-decrypter';
import * as utils from './bin-utils';
import { getStreamTime } from './util/time';
import { timeRangesToArray } from './ranges';
import { MediaSource, URL } from './mse/index';
import videojs from 'video.js';
@ -658,6 +659,14 @@ class HlsHandler extends Component {
}
super.dispose();
}
convertToStreamTime(time, callback) {
return getStreamTime({
playlist: this.masterPlaylistController_.media(),
time,
callback
});
}
}
/**

196
test/util/time.test.js

@ -0,0 +1,196 @@
import QUnit from 'qunit';
import videojs from 'video.js';
import {
getStreamTime
} from '../../src/util/time.js';
QUnit.module('Time: getStreamTime', {
beforeEach(assert) {
this.playlist = {
segments: [{
duration: 4,
// UTC: Sun, 11 Nov 2018 00:00:00 GMT
dateTimeObject: new Date(1541894400000),
dateTimeString: '2018-11-11T00:00:00.000Z',
start: 5,
end: 9
}]
};
},
afterEach(assert) {
delete this.playlist;
}
});
QUnit.test('returns error if playlist or time is not provided', function(assert) {
const done = assert.async();
const done2 = assert.async();
getStreamTime({
time: 1,
callback: (err, streamTime) => {
assert.equal(
err.message,
'getStreamTime: playlist and time must be provided',
'error message is returned when no playlist provided'
);
done();
}
});
getStreamTime({
playlist: this.playlist,
callback: (err, streamTime) => {
assert.equal(
err.message,
'getStreamTime: playlist and time must be provided',
'error message is returned when no playlist provided'
);
done2();
}
});
});
QUnit.test('throws error if no callback is provided', function(assert) {
assert.throws(
() => {
return getStreamTime({
time: 1,
playlist: this.playlist
});
},
/getStreamTime: callback must be provided/,
'throws error if callback is not provided'
);
});
QUnit.test('returns info to accept callback if accurate value can be returned',
function(assert) {
const done = assert.async();
getStreamTime({
playlist: this.playlist,
time: 6,
callback: (err, streamTime) => {
assert.notOk(
err,
'should not fail when accurate segment times are available'
);
assert.equal(
typeof streamTime,
'object',
'should return an object to onsuccess callback'
);
assert.ok(
streamTime.mediaSeconds !== undefined,
'mediaSeconds is passed to onsuccess'
);
assert.ok(
streamTime.programDateTime !== undefined,
'programDateTime is passed to onsuccess'
);
assert.equal(
streamTime.programDateTime,
this.playlist.segments[0].dateTimeString,
'uses programDateTime found in media segments'
);
done();
}
});
});
QUnit.test('return a seek time to reject callback if accurate value cannot be returned',
function(assert) {
const done = assert.async();
const playlist = {
segments: [
{
duration: 1,
// UTC: Sun, 11 Nov 2018 00:00:00 GMT
dateTimeObject: new Date(1541894400000),
dateTimeString: '2018-11-11T00:00:00.000Z'
},
{
duration: 2,
// UTC: Sun, 11 Nov 2018 00:00:00 GMT
dateTimeObject: new Date(1541894400000),
dateTimeString: '2018-11-11T00:00:00.000Z'
}
]
};
getStreamTime({
playlist,
time: 2,
callback: (err, streamTime) => {
assert.equal(
err.message,
'Accurate streamTime could not be determined. Please seek to e.seekTime and try again',
'error message is returned for seekTime'
);
assert.equal(
err.seekTime,
1,
'returns the approximate start time of the segment containing the time requested'
);
done();
}
});
});
QUnit.test('returns time if no modifications', function(assert) {
const done = assert.async();
const segment = videojs.mergeOptions(this.playlist.segments[0], {
duration: 2,
start: 3,
end: 5
});
const playlist = {
segments: [
segment
]
};
getStreamTime({
playlist,
time: 3,
callback: (err, streamTime) => {
assert.equal(err, null, 'no error');
assert.equal(
streamTime.mediaSeconds,
3,
'mediaSeconds is currentTime if no further modifications'
);
done();
}
});
});
QUnit.test('returns programDateTime parsed from media segment tags', function(assert) {
const done = assert.async();
const segment = videojs.mergeOptions(this.playlist.segments[0], {
duration: 1,
start: 0,
end: 1
});
const playlist = {
segments: [
segment
]
};
getStreamTime({
playlist,
time: 0,
callback: (err, streamTime) => {
assert.equal(err, null, 'no error');
assert.equal(
streamTime.programDateTime,
playlist.segments[0].dateTimeString,
'uses programDateTime found in media segments'
);
done();
}
});
});

73
test/videojs-http-streaming.test.js

@ -5,6 +5,7 @@ import videojs from 'video.js';
import Events from 'video.js';
import QUnit from 'qunit';
import testDataManifests from './test-manifests.js';
import { muxed as muxedSegment } from './test-segments';
import {
useFakeEnvironment,
useFakeMediaSource,
@ -2798,7 +2799,8 @@ QUnit.test('passes useCueTags hls option to master playlist controller', functio
videojs.options.hls = origHlsOptions;
});
QUnit.test('populates quality levels list when available', function(assert) {
// TODO: This test fails intermittently. Turn on when fixed to always pass.
QUnit.skip('populates quality levels list when available', function(assert) {
this.player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
@ -2956,6 +2958,75 @@ QUnit.test('does not set source keySystems if keySystems not provided by source'
}, 'does not set source eme options');
});
QUnit.test('convertToStreamTime will return error if time is not buffered', function(assert) {
const done = assert.async();
this.player.src({
src: 'manifest/playlist.m3u8',
type: 'application/vnd.apple.mpegurl'
});
this.clock.tick(1);
openMediaSource(this.player, this.clock);
// master
this.standardXHRResponse(this.requests.shift());
// media.m3u8
this.standardXHRResponse(this.requests.shift());
this.player.vhs.convertToStreamTime(3, (err, streamTime) => {
assert.deepEqual(
err,
{
message:
'Accurate streamTime could not be determined. Please seek to e.seekTime and try again',
seekTime: 0
},
'error is returned as time is not buffered'
);
done();
});
});
QUnit.test('convertToStreamTime will return stream time if buffered', function(assert) {
const done = assert.async();
this.player.src({
src: 'manifest/master.m3u8',
type: 'application/vnd.apple.mpegurl'
});
this.clock.tick(1);
openMediaSource(this.player, this.clock);
this.player.tech_.hls.bandwidth = 20e10;
// master
this.standardXHRResponse(this.requests[0]);
// media.m3u8
this.standardXHRResponse(this.requests[1]);
// ts
this.standardXHRResponse(this.requests[2], muxedSegment());
// source buffer is mocked, so must manually trigger the video buffer
// video buffer is the first buffer created
this.player.vhs.masterPlaylistController_
.mediaSource.sourceBuffers[0].trigger('updateend');
this.clock.tick(1);
// ts
this.standardXHRResponse(this.requests[3], muxedSegment());
this.player.vhs.convertToStreamTime(0.01, (err, streamTime) => {
assert.notOk(err, 'no errors');
assert.equal(
streamTime.mediaSeconds,
0.01,
'returned the streamTime of the source'
);
done();
});
});
QUnit.module('HLS Integration', {
beforeEach(assert) {
this.env = useFakeEnvironment(assert);

Loading…
Cancel
Save