Skip to content

Commit

Permalink
New: timestamp unit for startAt (#691)
Browse files Browse the repository at this point in the history
  • Loading branch information
DanDeMicco authored Mar 6, 2018
1 parent bd97236 commit 79ec9da
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 13 deletions.
57 changes: 56 additions & 1 deletion src/lib/viewers/media/MediaBaseViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ const MEDIA_AUTOPLAY_CACHE_KEY = 'media-autoplay';
const MEDIA_VOLUME_INCREMENT = 0.05;
const EMIT_WAIT_TIME_IN_MILLIS = 100;
const SECONDS_UNIT_NAME = 'seconds';
const TIMESTAMP_UNIT_NAME = 'timestamp';
const INITIAL_TIME_IN_SECONDS = 0;
const ONE_MINUTE_IN_SECONDS = 60;
const ONE_HOUR_IN_SECONDS = 60 * ONE_MINUTE_IN_SECONDS;

class MediaBaseViewer extends BaseViewer {
/**
Expand Down Expand Up @@ -86,10 +89,12 @@ class MediaBaseViewer extends BaseViewer {
if (unit === SECONDS_UNIT_NAME) {
convertedValue = parseFloat(value, 10);

if (convertedValue < 0) {
if (Number.isNaN(convertedValue) || convertedValue < 0) {
// Negative values aren't allowed, start from beginning
return INITIAL_TIME_IN_SECONDS;
}
} else if (unit === TIMESTAMP_UNIT_NAME) {
convertedValue = this.convertTimestampToSeconds(value);
} else {
console.error('Invalid unit for start:', unit); // eslint-disable-line no-console
}
Expand Down Expand Up @@ -786,6 +791,56 @@ class MediaBaseViewer extends BaseViewer {
this.mediaControls.show();
return true;
}

/**
* Converts from a youtube style timestamp to seconds
*
* @param {string} timestamp - the youtube style timestamp eg. "1h2m3s"
* @return {number} - the time in seconds
*/
convertTimestampToSeconds(timestamp) {
let timeInSeconds = INITIAL_TIME_IN_SECONDS;
// Supports optional decimal points
const TIMESTAMP_REGEX = /^([0-9]+(\.[0-9]*)?[smh]?){1,3}$/;
const HOUR_REGEX = /[0-9]+(\.[0-9]*)?h/;
const MINUTE_REGEX = /[0-9]+(\.[0-9]*)?m/;
const SECOND_REGEX = /[0-9]+(\.[0-9]*)?s/;

if (!timestamp || !TIMESTAMP_REGEX.test(timestamp)) {
return timeInSeconds;
}

// Gets the first match for all the units
const hours = HOUR_REGEX.exec(timestamp);
const minutes = MINUTE_REGEX.exec(timestamp);
const seconds = SECOND_REGEX.exec(timestamp);

/**
* Validates a substring match and converts to float
*
* @param {string} match - the timestamp substring e.g. 1h, 2m, or 3s
* @return {number} - the number for the given unit
*/
const getValueOfMatch = (match) => {
// Strip off unit (h, m, s) and convert to float
const parsedMatch = parseFloat(match[0].slice(0, -1), 10);
return Number.isNaN(parsedMatch) ? 0 : parsedMatch;
};

if (hours) {
timeInSeconds += ONE_HOUR_IN_SECONDS * getValueOfMatch(hours);
}

if (minutes) {
timeInSeconds += ONE_MINUTE_IN_SECONDS * getValueOfMatch(minutes);
}

if (seconds) {
timeInSeconds += getValueOfMatch(seconds);
}

return timeInSeconds;
}
}

export default MediaBaseViewer;
165 changes: 153 additions & 12 deletions src/lib/viewers/media/__tests__/MediaBaseViewer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe('lib/viewers/media/MediaBaseViewer', () => {

return media.load().then(() => {
expect(media.mediaEl.autoplay).to.be.true;
})
});
});

it('should autoplay if enabled', () => {
Expand All @@ -147,7 +147,7 @@ describe('lib/viewers/media/MediaBaseViewer', () => {

return media.load().then(() => {
expect(media.autoplay).to.be.called;
})
});
});

it('should invoke startLoadTimer()', () => {
Expand Down Expand Up @@ -191,11 +191,14 @@ describe('lib/viewers/media/MediaBaseViewer', () => {
it('should emit the error and set a display message', () => {
sandbox.stub(media, 'emit');
const err = new Error('blah');
sandbox.mock(window.console).expects('error').withArgs(err);
sandbox
.mock(window.console)
.expects('error')
.withArgs(err);

media.errorHandler(err);

const [ event, error ] = media.emit.getCall(0).args;
const [event, error] = media.emit.getCall(0).args;
expect(event).to.equal('error');
expect(error).to.be.instanceof(PreviewError);
expect(error.code).to.equal('error_load_media');
Expand All @@ -206,7 +209,10 @@ describe('lib/viewers/media/MediaBaseViewer', () => {
it('should emit speed change if speed has changed', () => {
const speed = 2;
sandbox.stub(media, 'emit');
sandbox.stub(media.cache, 'get').withArgs('media-speed').returns(speed);
sandbox
.stub(media.cache, 'get')
.withArgs('media-speed')
.returns(speed);
media.mediaEl = document.createElement('video');
media.mediaEl.playbackRate = 1;

Expand All @@ -220,8 +226,14 @@ describe('lib/viewers/media/MediaBaseViewer', () => {
describe('handleVolume()', () => {
beforeEach(() => {
stubs.volume = 50;
stubs.has = sandbox.stub(media.cache, 'has').withArgs('media-volume').returns(true);
stubs.get = sandbox.stub(media.cache, 'get').withArgs('media-volume').returns(stubs.volume);
stubs.has = sandbox
.stub(media.cache, 'has')
.withArgs('media-volume')
.returns(true);
stubs.get = sandbox
.stub(media.cache, 'get')
.withArgs('media-volume')
.returns(stubs.volume);
stubs.debouncedEmit = sandbox.stub(media, 'debouncedEmit');
});

Expand Down Expand Up @@ -314,7 +326,6 @@ describe('lib/viewers/media/MediaBaseViewer', () => {
expect(media.mediaControls.addListener).to.be.calledWith('togglemute', sinon.match.func);
expect(media.mediaControls.addListener).to.be.calledWith('ratechange', sinon.match.func);
expect(media.mediaControls.addListener).to.be.calledWith('autoplaychange', sinon.match.func);

});
});

Expand Down Expand Up @@ -477,19 +488,17 @@ describe('lib/viewers/media/MediaBaseViewer', () => {

describe('removePauseEventListener()', () => {
it('should remove pause event listener if it exists', () => {
let pauseListener = null
let pauseListener = null;
media.mediaEl = { removeEventListener: sandbox.stub() };

media.pauseListener = pauseListener;
media.removePauseEventListener();
expect(media.mediaEl.removeEventListener).to.be.not.be.called
expect(media.mediaEl.removeEventListener).to.be.not.be.called;

pauseListener = () => {};
media.pauseListener = pauseListener;
media.removePauseEventListener();
expect(media.mediaEl.removeEventListener).to.be.calledWith('timeupdate', pauseListener);


});
});

Expand Down Expand Up @@ -967,4 +976,136 @@ describe('lib/viewers/media/MediaBaseViewer', () => {
expect(media.mediaControls.show.callCount).to.equal(0);
});
});

describe('getStartTimeInSeconds()', () => {
it('should parse seconds', () => {
const startAt = {
unit: 'seconds',
value: 55
};

expect(media.getStartTimeInSeconds(startAt)).to.equal(55);
});

it('should parse timestamp', () => {
const startAt = {
unit: 'timestamp',
value: '1m2s'
};

expect(media.getStartTimeInSeconds(startAt)).to.equal(62);
});

it('should return the default value if invalid unit', () => {
const startAt = {
unit: 'foo',
value: 55
};

expect(media.getStartTimeInSeconds(startAt)).to.equal(0);
});

it('should return the default value if invalid value', () => {
const startAt = {
unit: 'seconds',
value: 'foo'
};

expect(media.getStartTimeInSeconds(startAt)).to.equal(0);
});

it('should return the default value if invalid startAt', () => {
let startAt = {
value: 'foo'
};

expect(media.getStartTimeInSeconds(startAt)).to.equal(0);

startAt = {
unit: 'seconds'
};

expect(media.getStartTimeInSeconds(startAt)).to.equal(0);
});
});

describe('convertTimestampToSeconds()', () => {
const ONE_MINUTE_IN_SECONDS = 60;
const ONE_HOUR_IN_SECONDS = 60 * ONE_MINUTE_IN_SECONDS;

it('should parse the timestamp with just seconds', () => {
const timestamp = '3s';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(3);
});

it('should parse the timestamp with just seconds and ms as floating point', () => {
const timestamp = '3.5432s';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(3.5432);
});

it('should parse the timestamp with minutes, and seconds', () => {
const timestamp = '2m3s';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(2 * ONE_MINUTE_IN_SECONDS + 3);
});

it('should parse the timestamp with hours and seconds', () => {
const timestamp = '4h3s';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(4 * ONE_HOUR_IN_SECONDS + 3);
});

it('should parse the timestamp with just minutes', () => {
const timestamp = '4m';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(4 * ONE_MINUTE_IN_SECONDS);
});

it('should parse the timestamp with hours and minutes', () => {
const timestamp = '6h7m';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(
6 * ONE_HOUR_IN_SECONDS + 7 * ONE_MINUTE_IN_SECONDS
);
});

it('should parse the timestamp with just hours', () => {
const timestamp = '8h';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(8 * ONE_HOUR_IN_SECONDS);
});

it('should parse the timestamp with hours, minutes and seconds', () => {
const timestamp = '5h30m15s';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(
5 * ONE_HOUR_IN_SECONDS + 30 * ONE_MINUTE_IN_SECONDS + 15
);
});

it('should parse the timestamp with hours, minutes, and seconds', () => {
const timestamp = '5h30m15s';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(
5 * ONE_HOUR_IN_SECONDS + 30 * ONE_MINUTE_IN_SECONDS + 15
);
});

it('should parse the timestamp with hours, minutes, seconds (large values and decimal)', () => {
const timestamp = '5h75m653.546s';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(
5 * ONE_HOUR_IN_SECONDS + 75 * ONE_MINUTE_IN_SECONDS + 653.546
);
});

it('should return 0 if invalid string passed', () => {
let timestamp = '5h3m2s5d';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(0);

timestamp = '5h3m2ss';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(0);

timestamp = '5hms';
expect(media.convertTimestampToSeconds(timestamp)).to.equal(0);

expect(media.convertTimestampToSeconds()).to.equal(0);

expect(media.convertTimestampToSeconds('fdsfds')).to.equal(0);

expect(media.convertTimestampToSeconds('ah1m3s')).to.equal(0);
});
});
});

0 comments on commit 79ec9da

Please sign in to comment.