Skip to content

Commit

Permalink
fix: remove duplicate cues with same time interval and text (#1005)
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-barstow authored and gkatsev committed Nov 19, 2020
1 parent a82be45 commit fb1c909
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 5 deletions.
40 changes: 39 additions & 1 deletion src/util/text-tracks.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,47 @@ export const removeCuesFromTrack = function(start, end, track) {
while (i--) {
cue = track.cues[i];

// Remove any overlapping cue
// Remove any cue within the provided start and end time
if (cue.startTime >= start && cue.endTime <= end) {
track.removeCue(cue);
}
}
};

/**
* Remove duplicate cues from a track on video.js (a cue is considered a
* duplicate if it has the same time interval and text as another)
*
* @param {Object} track the text track to remove the duplicate cues from
* @private
*/
export const removeDuplicateCuesFromTrack = function(track) {
const cues = track.cues;

if (!cues) {
return;
}

for (let i = 0; i < cues.length; i++) {
const duplicates = [];
let occurrences = 0;

for (let j = 0; j < cues.length; j++) {
if (
cues[i].startTime === cues[j].startTime &&
cues[i].endTime === cues[j].endTime &&
cues[i].text === cues[j].text
) {
occurrences++;

if (occurrences > 1) {
duplicates.push(cues[j]);
}
}
}

if (duplicates.length) {
duplicates.forEach(dupe => track.removeCue(dupe));
}
}
};
12 changes: 9 additions & 3 deletions src/vtt-segment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import SegmentLoader from './segment-loader';
import videojs from 'video.js';
import window from 'global/window';
import { removeCuesFromTrack } from './util/text-tracks';
import { removeCuesFromTrack, removeDuplicateCuesFromTrack } from './util/text-tracks';
import { initSegmentId } from './bin-utils';
import { uint8ToUtf8 } from './util/string';
import { REQUEST_ERRORS } from './media-segment-request';
Expand Down Expand Up @@ -364,14 +364,20 @@ export default class VTTSegmentLoader extends SegmentLoader {

this.mediaSecondsLoaded += segment.duration;

// Create VTTCue instances for each cue in the new segment and add them to
// the subtitle track
segmentInfo.cues.forEach((cue) => {
// remove any overlapping cues to prevent doubling
this.remove(cue.startTime, cue.endTime);
this.subtitlesTrack_.addCue(this.featuresNativeTextTracks_ ?
new window.VTTCue(cue.startTime, cue.endTime, cue.text) :
cue);
});

// Remove any duplicate cues from the subtitle track. The WebVTT spec allows
// cues to have identical time-intervals, but if the text is also identical
// we can safely assume it is a duplicate that can be removed (ex. when a cue
// "overlaps" VTT segments)
removeDuplicateCuesFromTrack(this.subtitlesTrack_);

this.handleAppendsDone_();
}

Expand Down
84 changes: 83 additions & 1 deletion test/text-tracks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
createCaptionsTrackIfNotExists,
addCaptionData,
createMetadataTrackIfNotExists,
addMetadata
addMetadata,
removeDuplicateCuesFromTrack
} from '../src/util/text-tracks';

const { module, test } = Qunit;
Expand All @@ -16,6 +17,11 @@ class MockTextTrack {
addCue(cue) {
this.cues.push(cue);
}
removeCue(cue) {
const cueIndex = this.cues.map(c => c.text).indexOf(cue.text);

this.cues.splice(cueIndex, 1);
}
}

class MockTech {
Expand Down Expand Up @@ -302,3 +308,79 @@ test('adds cues for each metadata frame seen', function(assert) {
'ended at duration 20'
);
});

test('removeDuplicateCuesFromTrack removes all but one cue with identical startTime, endTime, and text', function(assert) {
const track = new MockTextTrack();

[{
startTime: 0,
endTime: 1,
text: 'CC1 text'
}, {
startTime: 1,
endTime: 2,
text: 'Identical'
}, {
startTime: 1,
endTime: 2,
text: 'Identical'
}, {
startTime: 1,
endTime: 2,
text: 'Identical'
}, {
startTime: 1,
endTime: 2,
text: 'Identical'
}, {
startTime: 2,
endTime: 3,
text: 'CC3 text'
}].forEach((mockCue) => {
track.addCue(mockCue);
});

assert.equal(track.cues.length, 6, '6 cues present initially');

removeDuplicateCuesFromTrack(track);

assert.equal(track.cues.length, 3, '3 cue remains after duplicates removed');
});

test('removeDuplicateCuesFromTrack leaves in cues with the same startTime and endTime, but different text-- or vice-versa', function(assert) {
const track = new MockTextTrack();

[{
startTime: 0,
endTime: 1,
text: 'Identical'
}, {
startTime: 0,
endTime: 1,
text: 'Identical'
}, {
startTime: 0,
endTime: 1,
text: 'CC2 text'
}, {
startTime: 0,
endTime: 1,
text: 'CC3 text'
}, {
startTime: 1,
endTime: 2,
text: 'Also identical'
}, {
startTime: 1,
endTime: 2,
text: 'Also identical'
}].forEach((mockCue) => {
track.addCue(mockCue);
});

assert.equal(track.cues.length, 6, '6 cues present initially');

removeDuplicateCuesFromTrack(track);

assert.equal(track.cues.length, 4, '4 cues remain after duplicates removed');
});

0 comments on commit fb1c909

Please sign in to comment.