Skip to content

Commit

Permalink
Modify Offline module API (#3275)
Browse files Browse the repository at this point in the history
* Modify Offline module API:
- access offline module using MediaPlayer.getOfflineController()
- modify OfflineController API ("record" instead of "download")
- inject dependencies when creating OfflineController
- use MediaInfo type to get and select the available tracks representations to record/download
- add offline module types in index.d.ts
- update jsdoc
- update sample

* Offline module: rename url scheme ('offline_indexeddb')

* Offline module: update jsdoc

* DashParser: inject debug instance

* Offline module:
- store original manifest as a string in indexedDB instead of full object (can lead to DataCloneError)
- use DashParser to parse stored manifest when loading/resuming download of a record

* Offline module: update jsdoc

(cherry picked from commit 5e6b73e)

* user sources.json from reference sample

* fix code syntax and clean code

* offline sample: remov offLineController from global scope
  • Loading branch information
Bertrand Berthelot committed Jun 8, 2020
1 parent b6af24f commit d968368
Show file tree
Hide file tree
Showing 26 changed files with 657 additions and 1,742 deletions.
81 changes: 77 additions & 4 deletions index.d.ts
Expand Up @@ -44,7 +44,29 @@ declare namespace dashjs {
reset(): void;
}

export interface OfflineRecord {
id: string;
progress: number;
url: string;
originalUrl: string;
status: string;
}

interface OfflineController {
loadRecordsFromStorage(): Promise<void>;
getAllRecords(): OfflineRecord[];
createRecord(manifestURL: string): Promise<string>;
startRecord(id: string, mediaInfos: MediaInfo[]);
stopRecord(id: string): void;
resumeRecord(id: string): void;
deleteRecord(id: string): void;
getRecordProgression(id: string): number;
resetRecords(): void;
reset(): void;
}

export interface Bitrate {
id?: string;
width?: number;
height?: number;
bandwidth?: number;
Expand Down Expand Up @@ -195,7 +217,8 @@ declare namespace dashjs {
on(type: ManifestLoadedEvent['type'], listener: (e: ManifestLoadedEvent) => void, scope?: object): void;
on(type: MetricEvent['type'], listener: (e: MetricEvent) => void, scope?: object): void;
on(type: MetricChangedEvent['type'], listener: (e: MetricChangedEvent) => void, scope?: object): void;
on(type: OfflineStreamEvent['type'], listener: (e: OfflineStreamEvent) => void, scope?: object): void;
on(type: OfflineRecordEvent['type'], listener: (e: OfflineRecordEvent) => void, scope?: object): void;
on(type: OfflineRecordLoademetadataEvent['type'], listener: (e: OfflineRecordLoademetadataEvent) => void, scope?: object): void;
on(type: PeriodSwitchEvent['type'], listener: (e: PeriodSwitchEvent) => void, scope?: object): void;
on(type: PlaybackErrorEvent['type'], listener: (e: PlaybackErrorEvent) => void, scope?: object): void;
on(type: PlaybackPausedEvent['type'], listener: (e: PlaybackPausedEvent) => void, scope?: object): void;
Expand Down Expand Up @@ -283,6 +306,7 @@ declare namespace dashjs {
getProtectionController(): ProtectionController;
attachProtectionController(value: ProtectionController): void;
setProtectionData(value: ProtectionData): void;
getOfflineController(): OfflineController;
enableManifestDateHeaderTimeSource(value: boolean): void;
displayCaptionsOnTop(value: boolean): void;
attachTTMLRenderingDiv(div: HTMLDivElement): void;
Expand Down Expand Up @@ -352,7 +376,24 @@ declare namespace dashjs {
KEY_SYSTEM_ACCESS_DENIED_ERROR_CODE: 112;
KEY_SESSION_CREATED_ERROR_CODE: 113;
MEDIA_KEY_MESSAGE_LICENSER_ERROR_CODE: 114;
// MSS errors
MSS_NO_TFRF_CODE: 200;
MSS_UNSUPPORTED_CODEC_CODE: 201;
// Offline errors
OFFLINE_ERROR: 11000;
INDEXEDDB_QUOTA_EXCEED_ERROR: 11001;
INDEXEDDB_INVALID_STATE_ERROR: 11002;
INDEXEDDB_NOT_READABLE_ERROR: 11003;
INDEXEDDB_NOT_FOUND_ERROR: 11004;
INDEXEDDB_NETWORK_ERROR: 11005;
INDEXEDDB_DATA_ERROR: 11006;
INDEXEDDB_TRANSACTION_INACTIVE_ERROR: 11007;
INDEXEDDB_NOT_ALLOWED_ERROR: 11008;
INDEXEDDB_NOT_SUPPORTED_ERROR: 11009;
INDEXEDDB_VERSION_ERROR: 11010;
INDEXEDDB_TIMEOUT_ERROR: 11011;
INDEXEDDB_ABORT_ERROR: 11012;
INDEXEDDB_UNKNOWN_ERROR: 11013;
}

interface MediaPlayerEvents {
Expand Down Expand Up @@ -382,6 +423,10 @@ declare namespace dashjs {
METRIC_ADDED: 'metricAdded';
METRIC_CHANGED: 'metricChanged';
METRIC_UPDATED: 'metricUpdated';
OFFLINE_RECORD_FINISHED: 'public_offlineRecordFinished';
OFFLINE_RECORD_LOADEDMETADATA: 'public_offlineRecordLoadedmetadata';
OFFLINE_RECORD_STARTED: 'public_offlineRecordStarted';
OFFLINE_RECORD_STOPPED: 'public_offlineRecordStopped';
PERIOD_SWITCH_COMPLETED: 'periodSwitchCompleted';
PERIOD_SWITCH_STARTED: 'periodSwitchStarted';
PLAYBACK_ENDED: 'playbackEnded';
Expand Down Expand Up @@ -496,6 +541,7 @@ declare namespace dashjs {
MediaPlayerErrors['TIMED_TEXT_ERROR_ID_PARSE_CODE'] |
MediaPlayerErrors['MANIFEST_ERROR_ID_MULTIPLEXED_CODE'] |
MediaPlayerErrors['MEDIASOURCE_TYPE_UNSUPPORTED_CODE'] |
// Protection errors
MediaPlayerErrors['MEDIA_KEYERR_CODE'] |
MediaPlayerErrors['MEDIA_KEYERR_UNKNOWN_CODE'] |
MediaPlayerErrors['MEDIA_KEYERR_CLIENT_CODE'] |
Expand All @@ -511,9 +557,26 @@ declare namespace dashjs {
MediaPlayerErrors['KEY_SYSTEM_ACCESS_DENIED_ERROR_CODE'] |
MediaPlayerErrors['KEY_SESSION_CREATED_ERROR_CODE'] |
MediaPlayerErrors['MEDIA_KEY_MESSAGE_LICENSER_ERROR_CODE'] |
MediaPlayerErrors['MSS_NO_TFRF_CODE'],
message:string,
data:object,
// Offline errors
MediaPlayerErrors['OFFLINE_ERROR'] |
MediaPlayerErrors['INDEXEDDB_QUOTA_EXCEED_ERROR'] |
MediaPlayerErrors['INDEXEDDB_INVALID_STATE_ERROR'] |
MediaPlayerErrors['INDEXEDDB_NOT_READABLE_ERROR'] |
MediaPlayerErrors['INDEXEDDB_NOT_FOUND_ERROR'] |
MediaPlayerErrors['INDEXEDDB_NETWORK_ERROR'] |
MediaPlayerErrors['INDEXEDDB_DATA_ERROR'] |
MediaPlayerErrors['INDEXEDDB_TRANSACTION_INACTIVE_ERROR'] |
MediaPlayerErrors['INDEXEDDB_NOT_ALLOWED_ERROR'] |
MediaPlayerErrors['INDEXEDDB_NOT_SUPPORTED_ERROR'] |
MediaPlayerErrors['INDEXEDDB_VERSION_ERROR'] |
MediaPlayerErrors['INDEXEDDB_TIMEOUT_ERROR'] |
MediaPlayerErrors['INDEXEDDB_ABORT_ERROR'] |
MediaPlayerErrors['INDEXEDDB_UNKNOWN_ERROR'] |
// MSS errors
MediaPlayerErrors['MSS_NO_TFRF_CODE'] |
MediaPlayerErrors['MSS_UNSUPPORTED_CODEC_CODE'],
message: string,
data: object,
}
}

Expand Down Expand Up @@ -621,6 +684,16 @@ declare namespace dashjs {
mediaType: MediaType;
}

export interface OfflineRecordEvent extends Event {
type: MediaPlayerEvents['OFFLINE_RECORD_FINISHED' | 'OFFLINE_RECORD_STARTED' | 'OFFLINE_RECORD_STOPPED' | 'OFFLINE_RECORD_STOPPED'];
id: string;
}

export interface OfflineRecordLoademetadataEvent extends Event {
type: MediaPlayerEvents['OFFLINE_RECORD_LOADEDMETADATA'];
madiaInfos: MediaInfo[];
}

export interface PeriodSwitchEvent extends Event {
type: MediaPlayerEvents['PERIOD_SWITCH_COMPLETED' | 'PERIOD_SWITCH_STARTED'];
toStreamInfo: StreamInfo | null;
Expand Down
66 changes: 39 additions & 27 deletions samples/offline/app/main.js
Expand Up @@ -18,7 +18,7 @@ $(document).ready(function () {
});

angular.module('DashSourcesService', ['ngResource']).factory('sources', function ($resource) { /* jshint ignore:line */
return $resource('app/sources.json', {}, {
return $resource('../dash-if-reference-player/app/sources.json', {}, {
query: {
method: 'GET',
isArray: false
Expand Down Expand Up @@ -263,8 +263,8 @@ app.controller('DashController', function ($scope, $timeout, $q, sources, contri
if ($scope.player) {
$scope.player.updateSettings(config);
}
if ($scope.downloader) {
$scope.downloader.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_DEBUG } });
if ($scope.offlinePlayer) {
$scope.offlinePlayer.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_DEBUG } });
}
} else {
// Set default initial configuration
Expand All @@ -283,7 +283,7 @@ app.controller('DashController', function ($scope, $timeout, $q, sources, contri
}
};
$scope.player.updateSettings(initialConfig);
$scope.downloader.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_INFO } });
$scope.offlinePlayer.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_INFO } });
}
};
reqConfig.open('GET', 'dashjs_config.json', true);
Expand Down Expand Up @@ -340,7 +340,6 @@ app.controller('DashController', function ($scope, $timeout, $q, sources, contri
}
}, $scope);


$scope.player.initialize($scope.video, null, $scope.autoPlaySelected);

// Add HTML-rendered TTML subtitles except for Firefox < v49 (issue #1164)
Expand Down Expand Up @@ -438,16 +437,16 @@ app.controller('DashController', function ($scope, $timeout, $q, sources, contri
// Download setup
//
////////////////////////////////////////
$scope.downloader = dashjs.MediaPlayer().create(); /* jshint ignore:line */
$scope.downloader.initialize($scope.video, null, $scope.autoPlaySelected);
$scope.offlinePlayer = dashjs.MediaPlayer().create(); /* jshint ignore:line */
$scope.offlinePlayer.initialize($scope.video, null, $scope.autoPlaySelected);

$scope.downloads = DownloadService.getDownloads();
DownloadService.init($scope.downloader);
DownloadService.init($scope.offlinePlayer);

$scope.downloader.on(dashjs.MediaPlayer.events.DOWNLOADABLE_REPRESENTATIONS_LOADED, function (e) { /* jshint ignore:line */
console.log(JSON.stringify(e.data));
$scope.downloadableRepresentations = e.data.downloadableRepresentations;
$scope.manifestId = e.data.id;
$scope.offlinePlayer.on(dashjs.MediaPlayer.events.OFFLINE_RECORD_LOADEDMETADATA, function (e) { /* jshint ignore:line */
console.log(JSON.stringify(e));
$scope.mediaInfos = e.mediaInfos;
$scope.manifestId = e.id;
$scope.showRepresentationModal();
}, $scope);

Expand Down Expand Up @@ -670,32 +669,32 @@ app.controller('DashController', function ($scope, $timeout, $q, sources, contri
switch (level) {
case 'none':
$scope.player.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_NONE } });
$scope.downloader.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_NONE } });
$scope.offlinePlayer.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_NONE } });
break;

case 'fatal':
$scope.player.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_FATAL } });
$scope.downloader.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_FATAL } });
$scope.offlinePlayer.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_FATAL } });
break;

case 'error':
$scope.player.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_ERROR } });
$scope.downloader.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_ERROR } });
$scope.offlinePlayer.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_ERROR } });
break;

case 'warning':
$scope.player.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_WARNING } });
$scope.downloader.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_WARNING } });
$scope.offlinePlayer.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_WARNING } });
break;

case 'info':
$scope.player.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_INFO } });
$scope.downloader.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_INFO } });
$scope.offlinePlayer.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_INFO } });
break;

default:
$scope.player.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_DEBUG } });
$scope.downloader.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_DEBUG } });
$scope.offlinePlayer.updateSettings({ 'debug': { 'logLevel': dashjs.Debug.LOG_LEVEL_DEBUG } });
}
};

Expand Down Expand Up @@ -1028,23 +1027,23 @@ app.controller('DashController', function ($scope, $timeout, $q, sources, contri
//
////////////////////////////////////////

$scope.player.on(dashjs.MediaPlayer.events.DOWNLOADING_STARTED, function (e) { /* jshint ignore:line */
$scope.player.on(dashjs.MediaPlayer.events.OFFLINE_RECORD_STARTED, function (e) { /* jshint ignore:line */
$scope.successMessage = e.message;
$('.alert.alert-success').show();
$('.alert.alert-success').fadeTo(2500, 500).slideUp(500, function () {
$('.alert.alert-success').slideUp(500);
});
}, $scope);

$scope.player.on(dashjs.MediaPlayer.events.DOWNLOADING_FINISHED, function (e) { /* jshint ignore:line */
$scope.player.on(dashjs.MediaPlayer.events.OFFLINE_RECORD_FINISHED, function (e) { /* jshint ignore:line */
$scope.successMessage = e.message;
$('.alert.alert-success').show();
$('.alert.alert-success').fadeTo(2500, 500).slideUp(500, function () {
$('.alert.alert-success').slideUp(500);
});
}, $scope);

$scope.player.on(dashjs.MediaPlayer.events.DOWNLOADING_STOPPED, function (e) { /* jshint ignore:line */
$scope.player.on(dashjs.MediaPlayer.events.OFFLINE_RECORD_STOPPED, function (e) { /* jshint ignore:line */
$scope.warningMessage = e.message;
$('.alert.alert-warning').show();
$('.alert.alert-warning').fadeTo(2500, 500).slideUp(500, function () {
Expand All @@ -1058,7 +1057,7 @@ app.controller('DashController', function ($scope, $timeout, $q, sources, contri

$scope.hideRepresentationModal = function () {
$scope.manifestId = undefined;
$scope.downloadableRepresentations = null;
$scope.mediaInfos = null;
$('#representationModal').modal('hide');
};

Expand Down Expand Up @@ -1092,13 +1091,26 @@ app.controller('DashController', function ($scope, $timeout, $q, sources, contri
return representations;
};

$scope.getSelectedMediaInfos = function () {
let mediaInfos = [];
$scope.mediaInfos.forEach(mediaInfo => {
let selected = false;
mediaInfo.bitrateList = mediaInfo.bitrateList.filter(bitrate => {
selected = selected || bitrate.selected;
return bitrate.selected;
});
if (selected) {
mediaInfos.push(mediaInfo);
}
});
return mediaInfos;
};

$scope.onStartDownload = function () {
let selectedRepresentation = $scope.getSelectedRepresentations();
let mediaInfos = $scope.getSelectedMediaInfos();

if (selectedRepresentation.video.length >= 1 ||
selectedRepresentation.audio.length >= 1 ||
selectedRepresentation.text.length >= 1) {
$scope.downloader.startDownload($scope.manifestId, selectedRepresentation);
if (mediaInfos.length) {
$scope.offlinePlayer.getOfflineController().startRecord($scope.manifestId, mediaInfos);
$scope.hideRepresentationModal();
} else {
alert('You must select at least 1 quality !');
Expand Down
10 changes: 5 additions & 5 deletions samples/offline/app/offline/directives/download.js
Expand Up @@ -14,7 +14,7 @@ angular.module('DashPlayer').
scope.progressTimer = null;
scope.downloadProgression = DownloadService.getDownloadProgression(scope.download.id);
scope.isEnabled = true;

scope.$watch('download.status', function (newValue) {
if (newValue === 'created') {
scope.onCreated(scope);
Expand All @@ -35,16 +35,16 @@ angular.module('DashPlayer').
};

scope.doStop = function () {
DownloadService.doStopDownload(scope.download.id);
DownloadService.doStopRecord(scope.download.id);
};

scope.doResume = function () {
DownloadService.doResumeDownload(scope.download.id);
DownloadService.doResumeRecord(scope.download.id);
};

scope.doDelete = function () {
scope.isEnabled = false;
DownloadService.doDeleteDownload(scope.download.id);
DownloadService.doDeleteRecord(scope.download.id);
};

scope.updateDownloadProgression = function () {
Expand All @@ -56,7 +56,7 @@ angular.module('DashPlayer').

scope.isDownloadEnabled = function () {
return scope.isEnabled;
};
};

scope.canPlay = function () {
return scope.download.status === 'stopped' ||
Expand Down

0 comments on commit d968368

Please sign in to comment.