Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 9 additions & 1 deletion lib/fragment-list-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const AudioSample = require('./audio-sample');
const VideoSample = require('./video-sample');
const FragmentList = require('./fragment-list');

const VIDEO_DURATION_THRESHOLD = 0.9;
const AUDIO_DURATION_THRESHOLD = 1.0;

class FragmentListBuilder {

static build(movie, fragmentDuration) {
Expand Down Expand Up @@ -56,7 +59,11 @@ class FragmentListBuilder {
let sample = null;
let prevSampleVideoTimestamp = -1;
let prevSampleAudioTimestamp = -1;

const targetFragmentDuration = fragmentList.timescale * fragmentDuration;
const threshold = videoTrack ? VIDEO_DURATION_THRESHOLD : AUDIO_DURATION_THRESHOLD;
const cutOffDuration = targetFragmentDuration * threshold;

for (let i = 0, l = samples.length; i < l; i++) {
// remember previous sample timestamp
if (sample !== null) {
Expand All @@ -69,9 +76,10 @@ class FragmentListBuilder {

sample = samples[i];
timestamp = fragmentList.timescale * sample.timestamp / sample.timescale;

if (!videoTrack || (sample instanceof VideoSample && sample.keyframe)) {
duration = timestamp - timebase;
if (duration >= targetFragmentDuration) {
if (duration >= cutOffDuration) {
timebase = timestamp;
fragment.duration = duration;
fragment.samples = samples.slice(pos, i);
Expand Down
194 changes: 194 additions & 0 deletions test/fragment-list-builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
'use strict';

const VideoLib = require('../index');
const FragmentListBuilder = VideoLib.FragmentListBuilder;

const Movie = VideoLib.Movie;
const VideoTrack = VideoLib.VideoTrack;
const AudioTrack = VideoLib.AudioTrack;
const VideoSample = VideoLib.VideoSample;
const AudioSample = VideoLib.AudioSample;

const chai = require('chai');
const expect = chai.expect;

const MOVIE_TIMESCALE = 1000;
const VIDEO_TIMESCALE = 12800;
const AUDIO_TIMESCALE = 44100;

describe('FragmentListBuilder', function () {

describe('#build()', function () {

beforeEach(function () {
this.movie = new Movie();
this.movie.timescale = MOVIE_TIMESCALE;
});

const getRelativeTimestamps = (fragment, SampleClass) =>
fragment.samples
.filter(s => s instanceof SampleClass)
.map(s => s.relativeTimestamp());

it('should not split to chunks when movie without tracks', function () {
const fragmentDuration = 10;
const fragmentList = FragmentListBuilder.build(this.movie, fragmentDuration);

expect(fragmentList.fragmentDuration).to.be.equal(fragmentDuration);
expect(fragmentList.timescale).to.be.equal(MOVIE_TIMESCALE);
expect(fragmentList.duration).to.be.equal(0);
expect(fragmentList.count()).to.be.equal(0);
});

describe('when movie has both audio and video tracks', function () {
beforeEach(function () {
this.videoTrack = new VideoTrack();
this.videoTrack.timescale = VIDEO_TIMESCALE;
this.movie.addTrack(this.videoTrack);

this.audioTrack = new AudioTrack();
this.audioTrack.timescale = AUDIO_TIMESCALE;
this.movie.addTrack(this.audioTrack);
});

it('should not split to chunks when movie without samples', function () {
const fragmentDuration = 10;
const fragmentList = FragmentListBuilder.build(this.movie, fragmentDuration);

expect(fragmentList.fragmentDuration).to.be.equal(fragmentDuration);
expect(fragmentList.timescale).to.be.equal(VIDEO_TIMESCALE);
expect(fragmentList.duration).to.be.equal(0);
expect(fragmentList.count()).to.be.equal(0);
});

it('should correctly split into chunks when movie has samples', function () {
const videoSamplesData = [
// chunk 1
[0, true], [3, true], [5, false],
// chunk 2
[9, true], [10, false], [11, true], [13, false], [15, false], [17, true], [18, false],
// chunk 3
[21, true], [23, false],
];

videoSamplesData.forEach(([timestamp, keyframe]) => {
const sample = new VideoSample();
sample.timescale = VIDEO_TIMESCALE;
sample.timestamp = timestamp * VIDEO_TIMESCALE;
sample.keyframe = keyframe;
this.videoTrack.samples.push(sample);
});

for (let i = 0; i < 25; i++) {
const sample = new AudioSample();
sample.timescale = AUDIO_TIMESCALE;
sample.timestamp = i * AUDIO_TIMESCALE;
this.audioTrack.samples.push(sample);
}

const fragmentDuration = 10;
const fragmentList = FragmentListBuilder.build(this.movie, fragmentDuration);

expect(fragmentList.duration).to.be.equal(25 * VIDEO_TIMESCALE);
expect(fragmentList.count()).to.be.equal(3);

const expectedFragments = [
{
duration: 9,
video: [0, 3, 5],
audio: [0, 1, 2, 3, 4, 5, 6, 7, 8],
},
{
duration: 12,
video: [9, 10, 11, 13, 15, 17, 18],
audio: [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
},
{
duration: 4,
video: [21, 23],
audio: [21, 22, 23, 24],
},
];

expectedFragments.forEach((expected, index) => {
const fragment = fragmentList.get(index);

expect(fragment.relativeDuration())
.to.be.equal(expected.duration, `Fragment ${index} duration mismatch`);

expect(getRelativeTimestamps(fragment, VideoSample))
.to.deep.equal(expected.video, `Fragment ${index} video timestamps mismatch`);

expect(getRelativeTimestamps(fragment, AudioSample))
.to.deep.equal(expected.audio, `Fragment ${index} audio timestamps mismatch`);
});
});
});

describe('when movie has only audio track', function () {
beforeEach(function () {
this.audioTrack = new AudioTrack();
this.audioTrack.timescale = AUDIO_TIMESCALE;
this.movie.addTrack(this.audioTrack);
});

it('should not split to chunks when movie without samples', function () {
const fragmentDuration = 10;
const fragmentList = FragmentListBuilder.build(this.movie, fragmentDuration);

expect(fragmentList.fragmentDuration).to.be.equal(fragmentDuration);
expect(fragmentList.timescale).to.be.equal(AUDIO_TIMESCALE);
expect(fragmentList.duration).to.be.equal(0);
expect(fragmentList.count()).to.be.equal(0);
});

it('should correctly split into chunks when movie has samples', function () {
for (let i = 0; i < 25; i++) {
const sample = new AudioSample();
sample.timescale = AUDIO_TIMESCALE;
sample.timestamp = i * AUDIO_TIMESCALE;
this.audioTrack.samples.push(sample);
}

const fragmentDuration = 10;
const fragmentList = FragmentListBuilder.build(this.movie, fragmentDuration);

expect(fragmentList.duration).to.be.equal(25 * AUDIO_TIMESCALE);
expect(fragmentList.count()).to.be.equal(3);

const expectedFragments = [
{
duration: 10,
video: [],
audio: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
},
{
duration: 10,
video: [],
audio: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
},
{
duration: 5,
video: [],
audio: [20, 21, 22, 23, 24],
},
];

expectedFragments.forEach((expected, index) => {
const fragment = fragmentList.get(index);

expect(fragment.relativeDuration())
.to.be.equal(expected.duration, `Fragment ${index} duration mismatch`);

expect(getRelativeTimestamps(fragment, VideoSample))
.to.deep.equal(expected.video, `Fragment ${index} video timestamps mismatch`);

expect(getRelativeTimestamps(fragment, AudioSample))
.to.deep.equal(expected.audio, `Fragment ${index} audio timestamps mismatch`);
});
});
});

});

});
1 change: 1 addition & 0 deletions test/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ describe('node-video-lib', function () {
require('./base-classes');
require('./buffer-utils');
require('./flv-parser');
require('./fragment-list-builder');
require('./fragment-list');
require('./fragment-reader');
require('./hls-packetizer');
Expand Down