Skip to content

Commit

Permalink
feat(srgssr-middleware): add chapters support
Browse files Browse the repository at this point in the history
Resolves #204 by allowing developers to access chapters directly from the
player's text tracks.

Chapters can be accessed in two ways. The first is raw data via the `mediaData`
object from player `currentSource`. The second is via the player's `textTracks`.
This last method allows to listen to the cuechange event and react as
accordingly.

```javascript
// Accessing raw data
player.currentSource().mediaData.chapters;

// Listen for cuechange events and get the active cue
player.textTracks().getTrackById('srgssr-chapters').on('cuechange', ()=>{
	const [active] = Array.from(player.textTracks().getTrackById('srgssr-chapters').activeCues);
  JSON.parse(active); // note that active can be undefined
});
```

- add the `chapters` property to `mediaComposition`
- add a function to add `chapters` to the `player`
- add the `chapters` when resolving the resource in the `setSource` function
- add test cases
- add test media containing multiple chapters
  • Loading branch information
amtins committed Feb 23, 2024
1 parent dbe9eb4 commit 62ff02a
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 0 deletions.
9 changes: 9 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@
const resize = searchParams.has("resize");
const urn = searchParams.get("urn") || "urn:swi:video:48864812";

// Media examples
window.mediaExamples = {
_blockedSegmentAndChapters: {
label: 'Blocked segment at 761 (12:42) and chapters',
src: 'urn:rts:video:10894383',
type: 'srgssr/urn'
},
};

// Expose Pillarbox and player in the window object for debugging
window.pillarbox = Pillarbox;

Expand Down
1 change: 1 addition & 0 deletions src/dataProvider/model/MediaComposition.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class MediaComposition {
resource.analyticsMetadata
),
blockReason: this.getMainChapter().blockReason,
chapters: this.getChapters(),
vendor: this.getMainChapter().vendor,
drmList: resource.drmList,
dvr: resource.dvr,
Expand Down
37 changes: 37 additions & 0 deletions src/middleware/srgssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,42 @@ class SrgSsr {
});
}

/**
* Adds chapters to the player.
*
* @param {import('video.js/dist/types/player').default} player
* @param {string} chapterUrn The URN of the main chapter.
* @param {Array} [chapters=[]]
*/
static addChapters(player, chapterUrn, chapters = []) {
const trackId = 'srgssr-chapters';
const removeTrack = player.textTracks().getTrackById(trackId);

if (removeTrack) {
player.textTracks().removeTrack(removeTrack);
}

if (!Array.isArray(chapters) || !chapters.length) return;

const chapterTrack = new Pillarbox.TextTrack({
tech: player.tech(true),
kind: 'metadata',
id: trackId
});

chapters.forEach(chapter => {
if (chapterUrn !== chapter.fullLengthUrn) return;

chapterTrack.addCue({
startTime: chapter.fullLengthMarkIn / 1_000,
endTime: chapter.fullLengthMarkOut / 1_000,
text: JSON.stringify(chapter)
});
});

player.textTracks().addTrack(chapterTrack);
}

/**
* Set a blocking reason according to the block reason returned
* by mediaData.
Expand Down Expand Up @@ -331,6 +367,7 @@ class SrgSsr {
return;

SrgSsr.addRemoteTextTracks(player, mediaData.subtitles);
SrgSsr.addChapters(player, mediaData.urn, mediaData.chapters);

return next(null, srcMediaObj);
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions test/dataProvider/model/MediaComposition.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ describe('MediaComposition', () => {

expect(resource).toHaveProperty('analyticsData');
expect(resource).toHaveProperty('analyticsMetadata');
expect(resource).toHaveProperty('chapters');
expect(resource).toHaveProperty('vendor');
expect(resource).toHaveProperty('dvr');
expect(resource).toHaveProperty('drmList');
Expand Down
71 changes: 71 additions & 0 deletions test/middleware/srgssr.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,83 @@ describe('SrgSsr', () => {
options: jest.fn().mockReturnValue({ srgOptions: {}, trackers: {}}),
poster: (url) => url,
src: jest.fn(),
tech: jest.fn(),
textTracks: jest.fn().mockReturnValue({ getTrackById: jest.fn(), removeTrack: jest.fn(), addTrack: jest.fn() }),
titleBar: {
update: ({ title, description }) => ({ title, description }),
},
};
});

/**
*****************************************************************************
* addChapters ***************************************************************
*****************************************************************************
*/
describe('addChapters', () => {
it('should not call addChapters if the chapters parameter is not an array or if the array is empty', async () => {
const spyOnPillarboxTextTrack = jest.spyOn(Pillarbox, 'TextTrack');

SrgSsr.addChapters(player, true);
SrgSsr.addChapters(player, null);
SrgSsr.addChapters(player, '');
SrgSsr.addChapters(player, undefined);

expect(spyOnPillarboxTextTrack).not.toHaveBeenCalled();
});

it('should remove chapters track if any', async () => {
const spyOnRemoveTrack = jest.spyOn(player.textTracks(), 'removeTrack');

player.textTracks().getTrackById.mockReturnValueOnce({});
SrgSsr.addChapters(player);

expect(spyOnRemoveTrack).toHaveBeenCalled();
});

it('should not add the chapter if the only available chapter is the main chapter', async () => {
const chapterUrn = 'urn:full:length';
const result = [];

Pillarbox.TextTrack
.prototype
.addCue
.mockImplementation((cue) => result.push(cue));

SrgSsr.addChapters(player, chapterUrn, [{
fullLengthMarkIn: 0,
fullLengthMarkOut: 10000
}]);

expect(result).toHaveLength(0);
});

it('should add all chapters that are not the main chapter', async () => {
const chapterUrn = 'urn:full:length';
const result = [];

Pillarbox.TextTrack
.prototype
.addCue
.mockImplementation((cue) => result.push(cue));

SrgSsr.addChapters(player, chapterUrn, [{
fullLengthMarkIn: 0,
fullLengthMarkOut: 10000
}, {
fullLengthUrn: chapterUrn,
fullLengthMarkIn: 2500,
fullLengthMarkOut: 5000
}, {
fullLengthUrn: chapterUrn,
fullLengthMarkIn: 6000,
fullLengthMarkOut: 9500
}]);

expect(result).toHaveLength(2);
});
});

/**
*****************************************************************************
* addRemoteTextTracks *******************************************************
Expand Down

0 comments on commit 62ff02a

Please sign in to comment.