diff --git a/docs/tutorials/index.json b/docs/tutorials/index.json index 913a87aef9..548f2bc456 100644 --- a/docs/tutorials/index.json +++ b/docs/tutorials/index.json @@ -7,13 +7,15 @@ { "drm-config": { "title": "DRM Configuration" } }, { "license-server-auth": { "title": "License Server Authentication" } }, { "license-wrapping": { "title": "License Wrapping" } }, - { "Coming Soon": { "children": [ - { "live": { "title": "Special Considerations for Live Streaming" } }, - { "offline": { "title": "Offline Storage and Playback" } } - ] } }, { "plugins": { "title": "Plugins and Customizing the Build" } }, { "manifest-parser": { "title": "Manifest Parser Plugins" } }, { "architecture": { "title": "Architecture Diagrams" } }, { "service-worker": { "title": "Service Worker Caching" } }, - { "upgrade": { "title": "Shaka v2 Upgrade Guide" } } + { "upgrade": { + "title": "Upgrade Guide", + "children": { + "upgrade-v1": { "title": "Upgrade Guide, v1 => v2.1" }, + "upgrade-v2-0": { "title": "Upgrade Guide, v2.0 => v2.1" } + } + } } ] diff --git a/docs/tutorials/plugins.md b/docs/tutorials/plugins.md index e934ee1e39..798209c45f 100644 --- a/docs/tutorials/plugins.md +++ b/docs/tutorials/plugins.md @@ -28,12 +28,16 @@ __Manifest parsers__ and {@link shaka.media.ManifestParser.registerParserByMime} - Default manifest parser plugins: - DASH: {@linksource shaka.dash.DashParser} + - HLS: {@linksource shaka.hls.HlsParser} __Subtitle/caption parsers__ - Selected by MIME type - Register with {@link shaka.media.TextEngine.registerParser} - Default text parser plugins: - - WebVTT: {@linksource shaka.media.VttTextParser} + - WebVTT: {@linksource shaka.media.VttTextParser} and + {@linksource shaka.media.Mp4VttParser} + - TTML: {@linksource shaka.media.TtmlTextParser} and + {@linksource shaka.media.Mp4TtmlParser} __Networking plugins__ - Selected by URI scheme (http, https, etc.) @@ -59,6 +63,8 @@ __Polyfills__ - prefixed EME implementations for IE 11 and very old versions of embedded Chrome/Chromium: {@linksource shaka.polyfill.MediaKeys} - Promise implementation for IE 11: {@linksource shaka.polyfill.Promise} + - variants of VTTCue and TextTrackCue constructors: + {@linksource shaka.polyfill.VTTCue} #### Excluding Default Plugins @@ -68,7 +74,7 @@ technically optional. For example, if you don't need WebVTT, you can exclude our VTT parser from the build to save space. Any VTT text streams found in a manifest would then be ignored. -*(At the time of this writing, our default plugins account for 43% of the size +*(At the time of this writing, our default plugins account for 54% of the size of our compiled library.)* Because each plugin's source file ends with a call to register itself with the diff --git a/docs/tutorials/upgrade-v1.md b/docs/tutorials/upgrade-v1.md new file mode 100644 index 0000000000..cb35456f45 --- /dev/null +++ b/docs/tutorials/upgrade-v1.md @@ -0,0 +1,727 @@ +# Shaka Upgrade Guide, v1.x => v2 + +This is a detailed guide for upgrading from Shaka Player v1 to v2. It is a bit +long to read from beginning to end, so feel free to skim or to search for the +class and method names you are using in your application. + + +#### What's New in v2? + +Shaka v2 has several improvements over v1, including: + - Support for multiple DASH Periods + - Support for DASH Location elements + - Support for DASH UTCTiming elements for clock synchronization + - Lower-latency startup + - Simplified API + - Better browser compatibility + - More detailed browser support test + - Numerical error code system + - Clears old data from the buffer to conserve memory + - Buffering state is independent of play/pause + - Distinguishes between subtitle and caption tracks + - Separate audio & text language preferences + - New plugin and build system to extend Shaka + - Cache-friendly networking + - Simpler, mobile-friendly demo app + + +#### Shaka Plugins + +Shaka v2 has a new, cleaner architecture than v1 based on plugins. In v2, +networking, manifest parsing, and subtitle/caption parsing are all plugins. + +We bundle some default plugins (HTTP support, DASH support, and WebVTT), and +we plan to expand this list in future releases. Application developers can +write their own plugins as well. Plugins can either be compiled into the +library, or they can live outside the library in the application. Application +developers can also customize the build to exclude any default plugins they +don't need. + +For a more in-depth discussion of plugins, check out {@tutorial plugins}. + + +#### Namespace + +In v1, the `Player` class was namespaced as `shaka.player.Player`. In v2, this +has been simplified to `shaka.Player`. + + +#### load() + +Before, you needed a `DashVideoSource` or other `IVideoSource` subclass to pass +to `player.load()`. The video source was constructed with a manifest URL: + +```js +// v1: +var player = new shaka.player.Player(video); +var videoSource = new shaka.player.DashVideoSource(manifestUri); +player.load(videoSource); +``` + +In v2, the entire video source concept is gone from the API. Now, you pass the +URL directly to the player, and it decides which manifest parser plugin to use +based on the file extension or MIME type: + +```js +// v2: +var player = new shaka.Player(video); +player.load(manifestUri); +``` + + +#### ContentProtection callbacks + +Shaka v1's `DashVideoSource` had a parameter for a `ContentProtection` callback. +This callback was required to play protected content because Shaka did not +interpret `ContentProtection` elements in the DASH manifest and could not +derive the license server URI automatically: + +```js +// v1: +function interpretContentProtection(schemeIdUri, contentProtectionElement) { + if (schemeIdUri.toLowerCase() == + 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed') { + // This is the UUID which represents Widevine. + return [{ + 'keySystem': 'com.widevine.alpha', + 'licenseServerUrl': '//widevine-proxy.appspot.com/proxy' + }]; + } else if (schemeIdUri.toLowerCase() == + 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95') { + // This is the UUID which represents PlayReady. + return [{ + 'keySystem': 'com.microsoft.playready', + 'licenseServerUrl': '//playready.directtaps.net/pr/svc/rightsmanager.asmx' + }]; + } else { + return null; + } +} +var player = new shaka.player.Player(video); +var videoSource = new shaka.player.DashVideoSource( + manifestUri, interpretContentProtection); +player.load(videoSource); +``` + +In v2, these callbacks are *only* required for *non-standard* ContentProtection +schemes, such as that used by YouTube's demo assets. For the 99% of you who are +using standard schemes, no callback is required. Simply `configure()` the +player with your license servers: + +```js +// v2: +var player = new shaka.Player(video); +player.configure({ + drm: { + servers: { + 'com.widevine.alpha': '//widevine-proxy.appspot.com/proxy' + 'com.microsoft.playready': '//playready.directtaps.net/pr/svc/rightsmanager.asmx' + } + } +}); +player.load(manifestUri); +``` + +For a more in-depth discussion of DRM configuration, see {@tutorial drm-config}. + +If you need to support a custom ContentProtection scheme, you can still do so +with a callback set through `player.configure()`: + +```js +// v2: +function interpretContentProtection(contentProtectionElement) { + if (contentProtectionElement.getAttribute('schemeIdUri') == + 'http://youtube.com/drm/2012/10/10') { + var configs = []; + for (....) { + configs.push({ + 'keySystem': keySystem, + // WATCH OUT: now called URI not URL + 'licenseServerUri': licenseServerUri + }); + } + return configs; + } +} + +var player = new shaka.Player(video); +player.configure({ + manifest: { + dash: { + customScheme: interpretContentProtection + } + } +}); +player.load(manifestUri); +``` + +For more on what you can specify for a custom scheme, see the docs for +{@link shakaExtern.DrmInfo}. + + +#### Detailed DrmInfo + +Shaka v1's ContentProtection callbacks could return a detailed DrmInfo object +with lots of EME-related and license-request-related settings: + +```js +// v1: +function interpretContentProtection(schemeIdUri, contentProtectionElement) { + if (schemeIdUri.toLowerCase() == + 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed') { + return [{ + 'keySystem': 'com.widevine.alpha', + 'licenseServerUrl': '//widevine-proxy.appspot.com/proxy', + + 'distinctiveIdentifierRequired': true, + 'persistentStateRequired': false, + 'serverCertificate': certificateUint8Array, + 'audioRobustness': 'HW_SECURE_ALL', + 'videoRobustness': 'HW_SECURE_ALL', + 'initData': { 'initDataType': 'cenc', 'initData': initDataUint8Array }, + + 'licensePreProcessor': licensePreProcessor, + 'licensePostProcessor': licensePostProcessor, + 'withCredentials': true + }]; + } else { + return null; + } +} +function licensePreProcessor(requestInfo) { + // Only called for license requests. + + // Wrap the body, which is an ArrayBuffer: + var newBody = wrapLicenseRequest(requestInfo.body); + requestInfo.body = newBody; + + // Add a header: + requestInfo.headers['foo'] = 'bar'; +} +function licensePostProcessor(license) { + // Only called for license responses. + // Unwrap the license, which is a Uint8Array, use/store the extra data: + var rawLicense = unwrapLicense(license); + // Now return the raw license, which is also a Uint8Array: + return rawLicense; +} +``` + +In v2, the EME settings have moved to the `drm.advanced` field of the config +object: + +```js +// v2: +player.configure({ + drm: { + advanced: { + 'com.widevine.alpha': { + distinctiveIdentifierRequired: true, + persistentStateRequired: true, + serverCertificate: certificateUint8Array, + audioRobustness: 'HW_SECURE_ALL', + videoRobustness: 'HW_SECURE_ALL', + // NOTE: initData is now an array of one or more overrides: + initData: [{ initDataType: 'cenc', initData: initDataUint8Array }] + } + } + } +}); +``` + +For a discussion of advanced DRM configuration, see {@tutorial drm-config}. + +Shaka v1's license-request-releated settings have moved to v2's network filters. + +Network filters are a generic filtering system for all networking, including +license requests and responses. They are more general and flexible, so they +take slightly more effort than the old preprocessor/postprocessor system. +However, in v2, you only need to filter your license traffic for two reasons: + - if you use cross-site credentials (v1's "withCredentials" flag) + - if you wrap/unwrap license requests and responses into some other format + +```js +// v2: +player.getNetworkingEngine().registerRequestFilter(licensePreProcessor); +player.getNetworkingEngine().registerResponseFilter(licensePostProcessor); + +function licensePreProcessor(type, request) { + // A generic filter for all requests, so filter on type LICENSE: + if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) return; + + // Equivalent to v1's 'withCredentials': true + request.allowCrossSiteCredentials = true; + + // Wrap the request data, which is an ArrayBuffer: + var newData = wrapLicenseRequest(request.data); + request.data = newData; + + // Add a header: + request.headers['foo'] = 'bar'; +} +function licensePostProcessor(type, response) { + // A generic filter for all responses, so filter on type LICENSE: + if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) return; + + // Unwrap the response, which is an ArrayBuffer, use/store the extra data: + var rawLicense = unwrapLicense(response.data); + // Instead of returning the raw license, store it back to the response: + response.data = rawLicense; + // Return nothing. +} +``` + +For more on request filters, see the docs for +{@link shaka.net.NetworkingEngine.RequestFilter}, {@link shakaExtern.Request}, +{@link shaka.net.NetworkingEngine.ResponseFilter}, {@link shakaExtern.Response}. + + +#### ClearKey configuration + +Shaka v1's `ContentProtection` callbacks could be used for ClearKey, but it +required you to craft both a data URI and fake init data in the correct format: + +```js +// v1: +function interpretContentProtection(schemeIdUri, contentProtectionElement) { + var keyid; // as Uint8Array + var key; // as Uint8Array + var keyObj = { + kty: 'oct', + kid: Uint8ArrayUtils.toBase64(keyid, false), + k: Uint8ArrayUtils.toBase64(key, false) + }; + var jwkSet = {keys: [keyObj]}; + var license = JSON.stringify(jwkSet); + var initData = { + 'initData': keyid, + 'initDataType': 'webm' + }; + var licenseServerUrl = 'data:application/json;base64,' + + window.btoa(license); + return [{ + 'keySystem': 'org.w3.clearkey', + 'licenseServerUrl': licenseServerUrl, + 'initData': initData + }]; +} +``` + +In v2, this has been simplified through `player.configure()`: + +```js +// v2: +player.configure({ + drm: { + clearKeys: { + 'deadbeefdeadbeefdeadbeefdeadbeef': '18675309186753091867530918675309', + '02030507011013017019023029031037': '03050701302303204201080425098033' + } + } +}); +``` + +For more on ClearKey setup, see {@tutorial drm-config}. + + +#### BandwidthEstimator and AbrManager + +Shaka v1's `DashVideoSource` had parameters for applications to inject custom +BandwidthEstimator and AbrManager implementations. We even recommended +injecting BandwithEstimator to get persisted estimates across playbacks: + +```js +// v1: +var player = new shaka.player.Player(video); +var bandwidthEstimator = new shaka.util.EWMABandwidthEstimator(); +var abrManager = new shaka.media.SimpleAbrManager(); +var videoSource = new shaka.player.DashVideoSource( + manifestUri, /* interpretContentProtection */ null, estimator, abrManager); +player.load(videoSource); +``` + +In v2, we rolled the BandwidthEstimator concept into AbrManager. It is no +longer necessary to inject an instance to persist estimates across playbacks, +and custom AbrManagers are now provided via `player.configure()`: + +```js +// v2: +var player = new shaka.Player(video); +var customAbrManager = new MyCustomAbrManager(); +player.configure({ + abr: { + manager: customAbrManager + } +}); +player.load(manifestUri); +``` + +For more on the AbrManager interface, see the docs for +{@link shakaExtern.AbrManager}. + + +#### Selecting tracks + +Shaka v1 had separate methods for each type of tracks: `getVideoTracks()`, +`getAudioTracks()`, and `getTextTracks()`, as well as `selectVideoTrack()`, +`selectAudioTrack()`, and `selectTextTrack()`. Tracks were selected by ID. + +```js +// v1: +var videoTracks = player.getVideoTracks(); +var i = /* choose an index somehow */; +player.selectVideoTrack(videoTracks[i].id); // id, specifically video +``` + +In Shaka v2, audio and video tracks are combined into a variant track. It is +not possible to select individual audio/video streams, you can only select a +specific variant as specified by the manifest. + +You can get the currently available tracks using `getVariantTracks()` and +`getTextTracks()`. To switch tracks, use `selectVariantTrack()` and +`selectTextTrack()`, passing in the whole track object. + +```js +// v2: +var variantTracks = player.getVariantTracks(); +var i = /* choose an index somehow */; +player.selectVariantTrack(variantTracks[i]); // whole track +``` + +In v1, you could show or hide text tracks with `player.enableTextTrack()`: + +```js +// v1: +player.enableTextTrack(true); +``` + +In v2, this becomes `player.setTextTrackVisibility()`: + +```js +// v2: +player.setTextTrackVisibility(true); +``` + +See also the {@link shakaExtern.Track} structure which is used for all track +types (variant and text). + + +#### Side-loading captions/subtitles + +In Shaka v1, you could side-load subtitles that were not present in the manifest +by calling `addExternalCaptions()` on the `DashVideoSource` before `load()`: + +```js +// v1: +var player = new shaka.player.Player(video); +var videoSource = new shaka.player.DashVideoSource(manifestUri); +videoSource.addExternalCaptions(textStreamUri, 'fr-CA', 'text/vtt'); +player.load(videoSource); +``` + +In v2, this is done on with `player.addTextTrack()` after load() is complete: + +```js +// v2: +var player = new shaka.Player(video); +player.load(manifestUri).then(function() { + player.addTextTrack(textStreamUri, 'fr-CA', 'caption', 'text/vtt'); +}); +``` + + +#### Playback start time + +Shaka v1's `player.setPlaybackStartTime()` would let you start playback at an +arbitrary timestamp. It had to be called before load(): + +```js +// v1: +player.setPlaybackStartTime(123.45); +player.load(manifestUri); +``` + +In v2, this is done through an optional parameter on `load()`: + +```js +// v2: +player.load(manifestUri, 123.45); +``` + + +#### Trick play + +Shaka v1 had `player.setPlaybackRate()` that could be used for trick play by +emulating negative rate support in `video.playbackRate`. If you used +v1's `setPlaybackRate()` for trick play, use v2's `player.trickPlay()`. For +other purposes, use `video.playbackRate` directly. + + +#### configure() + +Shaka v1 and v2 both have a `player.configure()` method. Here is a map of +settings in v1 and their equivalents in v2 (most of which are at a different +level of the configuration hierarchy): + + - `enableAdaptation` => `abr.enabled` + - `streamBufferSize` => `streaming.bufferingGoal` + - `licenseRequestTimeout` => `drm.retryParameters.timeout` + - `mpdRequestTimeout` => `manifest.retryParameters.timeout` + - `segmentRequestTimeout` => `streaming.retryParameters.timeout` + - `preferredLanguage` => split into `preferredAudioLanguage` and + `preferredTextLanguage` + - `restrictions` => (same name, see below) + - `liveStreamEndTimeout` => (not needed in v2) + - `disableCacheBustingEvenThoughItMayAffectBandwidthEstimation` => + (not needed, always cache-friendly) + +The `shaka.player.Restriction` type was replaced by a simple record type. So +instead of constructing an object, simply create an anonymous JavaScript object. +`minPixels`/`maxPixels` were added to limit total pixels. Also `minBandwidth` +and `maxBandwidth` were split into `minAudioBandwidth`, `maxAudioBandwidth`, +`minVideoBandwidth`, and `maxVideoBandwidth`, see +{@link shakaExtern.Restrictions}. + +For more information on configuration in v2, see {@tutorial config}, +{@tutorial network-and-buffering-config}, and {@tutorial drm-config}. + + +#### getStats() + +Shaka v1 had `player.getStats()`. Shaka v2 has a similar method, but it returns +a somewhat different structure. + +```js +// v1: +player.getStats() + +=> Object + streamStats: StreamStats // refers to currently selected video stream + videoWidth: number // pixels + videoHeight: number // pixels + videoMimeType: string + videoBandwidth: number // bits/sec + decodedFrames: number + droppedFrames: number + estimatedBandwidth: number // bits/sec + playTime: number // seconds + bufferingTime: number // seconds + playbackLatency: number // seconds + bufferingHistory: Array // of timestamps when we started buffering + bandwidthHistory: Array of Objects + timestamp: number // seconds, when bandwidth estimate was made + value: number // bandwidth estimate, bits/sec + streamHistory: Array of Objects + timestamp: number // seconds, when video stream changed + value: StreamStats // information about the selected stream, video only +``` + +Shaka v2 does not expose playback latency or a history of bandwidth estimates. +v2's `switchHistory` is more general than v1's `streamHistory`, and covers all +stream types: + +```js +// v2: +player.getStats() + +=> Object + width: number // pixels, current video track + height: number // pixels, current video track + streamBandwidth: number // bits/sec, total for all current streams + decodedFrames: number // same as v1 + droppedFrames: number // same as v1 + estimatedBandwidth: number // bits/sec, same as v1 + loadLatency: number, // seconds between load() and the video's 'loadend' event + playTime: number // seconds, same as v1 + bufferingTime: number // seconds, same as v1 + switchHistory: Array of Objects // replaces v1's streamHistory + timestamp: number // seconds, when the stream was selected + id: number // stream ID + type: string // 'variant' or 'text' + fromAdaptation: boolean // distinguishes between ABR and manual choices + stateChange: Array of Objects + timestamp: number // seconds, when the state changed + state: string // 'buffering', 'playing', 'paused', or 'ended' + duration: number // seconds in this state +``` + +For more on stats in v2, see {@link shakaExtern.Stats}. + + +#### Player events + +In both Shaka v1 and v2, `Player` emits events. Here is a map of events and +their properties in v1 and their equivalents in v2: + + - `error` => `error` + - `detail` (various types) => `detail` ({@link shaka.util.Error}) + - `type` (vague string) => `category` + (number, unambiguous {@link shaka.util.Error.Category error category}) + - `message` (vague string) => `code` + (number, unambiguous {@link shaka.util.Error.Code error code}) + - `bufferingStart` and `bufferingEnd` => combined into `buffering` + - (no v1 equivalent) => `buffering` + (boolean, true when we enter buffering state, false when we leave) + - (no v1 equivalent) => + `{@link shaka.Player.TextTrackVisibilityEvent texttrackvisibility}` + - `trackschanged` => `trackschanged` + - `seekrangechanged` => + (no v2 equivalent, use `{@link player.seekRange}()` when updating the UI) + - `adaptation` => `adaptation` + - `contentType` (string) => (no v2 equivalent) + - `size` (width:number, height:number) => (no v2 equivalent) + - `bandwidth` (number) => (no v2 equivalent) + +For more information on events, see the Events section of {@link shaka.Player}. + + +#### Browser support testing + +In Shaka v1, you could check if a browser was supported or not using +`shaka.player.Player.isBrowserSupported()`: + +```js +// v1: +if (!shaka.player.Player.isBrowserSupported()) { + // Show an error and abort. +} +``` + +In v2, the same method exists to detect support. For diagnostics there is a new +method that will get more detailed information about the browser. This will +involve making a number of queries to EME which may result in user prompts, +so it is suggested this only be used for diagnostics: + +```js +// v2: +if (!shaka.Player.isBrowserSupported()) { + // Show an error and abort. +} else { + // Only call this method if the browser is supported. + shaka.Player.probeSupport().then(function(support) { + // The check is asynchronous because the EME API is asynchronous. + // The support object contains much more information about what the browser + // offers, if you need it. For example, if you require both Widevine and + // WebM/VP9: + if (!support.drm['com.widevine.alpha'] || + !support.media['video/webm; codecs="vp9"']) { + // Show an error and abort. + } + }); +} +``` + +For more on the support object, check out {@link shakaExtern.SupportType}. +You can also see the full `probeSupport()` report for your browser at: +{@link http://shaka-player-demo.appspot.com/support.html} + + +#### HttpVideoSource + +There is no equivalent to v1's `HttpVideoSource`. Shaka v2 only supports +playback via MSE. This is much simpler and allowed us to remove many special +cases from the code. + + +#### Offline storage + +In v1, to store offline content you used an `OfflineVideoSource`. + +```js +// v1: +function chooseStreams() { + return Promise.resolve(trackIds); +} + +function onProgress(evt) { + console.log('Stored ' + evt.detail + '%'); +} + +var videoSource = new shaka.player.OfflineVideoSource(null, null); +videoSource.addEventListener('progress', onProgress); + +var promise = videoSource.store('https://url', 'en-US', null, chooseStreams); +promise.then(function(groupId) { + console.log('Stored at group ID ' + groupId); +}); +``` + +In v2 you use the `Storage` class to store offline content. + +```js +// v2: +function chooseTracks(allTracks) { + return filteredTracks; +} + +function onProgress(storedContent, percent) { + console.log('Stored ' + percent + '%'); +} + +var storage = new shaka.offline.Storage(player); +// Optional +storage.configure({ + trackSelectionCallback: chooseTracks, + progressCallback: onProgress +}); + +var promise = storage.store('https://url', {extra: 'data'}); +promise.then(function(storedContent) { + console.log('Can be loaded using url: ' + storedContent.offlineUri); +}); +``` + +#### Offline playback + +In v1, you also used `OfflineVideoSource` to load the stored content. + +```js +// v1: +var videoSource = new shaka.player.OfflineVideoSource(groupId, null); +player.load(videoSource); +``` + +In v2, you don't have to use any special types so long as you know the URL to +use. When storing the content, you get a special URL that is simply passed to +`load` like any other URL. You can also get the info by listing all stored +content. + +```js +// v2: +player.load(offlineUri); +``` + +#### Offline listing and deleting + +In v1, you used `OfflineVideoSource` to list and delete content. + +```js +// v1: +var videoSource = new shaka.player.OfflineVideoSource(null, null); + +videoSource.retrieveGroupIds().then(function(groupIds) { + console.log(groupIds[0]); +}); + +var sourceToDelete = new shaka.player.OfflineVideoSource(groupId, null); +sourceToDelete.deleteGroup().then(function() { + console.log('Done'); +}); +``` + +In v2, you use `Storage` to list and delete stored content. + +```js +// v2: +var storage = new shaka.offline.Storage(player); + +storage.list().then(function(storedContents) { + var firstInfo = storedContents[0]; + var url = firstInfo.offlineUri; + player.load(url); +}); + +storage.delete(storedContent).then(function() { + console.log('Done'); +}); +``` diff --git a/docs/tutorials/upgrade-v2-0.md b/docs/tutorials/upgrade-v2-0.md new file mode 100644 index 0000000000..2ad26a9880 --- /dev/null +++ b/docs/tutorials/upgrade-v2-0.md @@ -0,0 +1,291 @@ +# Shaka Upgrade Guide, v2.0 => v2.1 + +This is a detailed guide for upgrading from Shaka Player v2.0 to v2.1. +Feel free to skim or to search for the class and method names you are using in +your application. + + +#### What's New in v2.1? + +Shaka v2.1 introduces several improvements over v2.0, including: + - Basic HLS support + - DASH trick mode support + - Support for jumping gaps in the timeline + - Asynchronous network filters + - Additional stats and events from Player + - Indication of critical errors vs recoverable errors + + +#### Selecting tracks + +Shaka v2.0 had one method for listing tracks (`getTracks()`) and one method for +selecting tracks (`selectTrack()`). Audio, video, and text could all be +independently selected. + +```js +// v2.0: +var allTracks = player.getTracks(); +var videoTracks = allTracks.filter(function(t) { t.type == 'video'; }); +var i = /* choose an index somehow */; +player.selectTrack(videoTracks[i]); +``` + +In Shaka v2.1, audio and video tracks are combined into a variant track. It is +not possible to select individual audio/video streams, you can only select a +specific variant as specified by the manifest. This was necessary for us to +support HLS. Text tracks are independent of variant tracks. + +You can get the currently available tracks using `getVariantTracks()` and +`getTextTracks()`. To switch tracks, use `selectVariantTrack()` and +`selectTextTrack()`. + +```js +// v2.1: +var variantTracks = player.getVariantTracks(); +var i = /* choose an index somehow */; +player.selectVariantTrack(variantTracks[i]); +``` + +The v2.0 methods `getTracks()` and `selectTrack()` are still present in v2.1, +but they are deprecated and will be removed in v2.2. However, they are not +completely backward compatible because of the `type` field. If you are looking +for `'video'` or `'audio'` in the `type` field, your application will need to +be updated to handle `'variant'` instead. + +See also the {@link shakaExtern.Track} structure which is used for all track +types (variant and text). + + +#### Changing languages + +With Shaka v2.0, you could change languages using `configure()` and the +`preferredAudioLanguage` and `preferredTextLanguage` fields. This would affect +both the initial choice of language and the current language during playback. + +```js +// v2.0: +player.configure({ preferredAudioLanguage: 'fr-CA' }); +player.load(manifestUri); // Canadian French preferred for initial playback +player.configure({ preferredAudioLanguage: 'el' }); // switch to Greek +``` + +In Shaka v2.1, language selection during playback is explicit and separate from +the configuration. Configuration only affects the next call to `load()`, and +will not change languages during playback. + +To list available languages, we provide the `getAudioLanguages()` and +`getTextLanguages()` methods. To change languages during playback, use +`selectAudioLanguage()` and `selectTextLanguage()`. + +```js +// v2.1: +player.configure({ preferredAudioLanguage: 'fr-CA' }); +player.load(manifestUri); // Canadian French preferred for initial playback + +player.configure({ preferredAudioLanguage: 'el' }); // Greek, does nothing now +player.selectAudioLanguage('fa'); // switch to Farsi right now + +player.load(secondManifestUri); // Greek preferred for initial playback +``` + + +#### Interpretation of Segmented WebVTT Text + +Segmented WebVTT text is not well-defined by any spec. Consensus in the +community seems to be that timestamps should be relative to the segment start. + +In Shaka v2.0, we offered an option called `useRelativeCueTimestamps`. When +set, WebVTT text timestamps were interpreted as relative to the segment. When +not set, WebVTT text timestamps were intepreted as relative to the period. + +In Shaka v2.1, this option was removed. WebVTT text timestamps are now always +interpreted as relative to the segment start time. + +Non-segmented WebVTT text, MP4-embedded VTT, and TTML are not affected by this +change. + +For more information, see discussions here: + - {@link https://github.com/google/shaka-player/issues/480} + - {@link https://github.com/google/shaka-player/issues/726} + + +#### Plugin interface changes + +If you have taken advantage of Shaka v2's plugin APIs, you may need to update +your plugins to the new interfaces. + +In v2.1, the v2.0 interfaces for text and manifest parsers are still supported, +but are deprecated. Support will be removed in v2.2. + + +#### Text parser plugin changes + +Text parser plugins have a new interface. The old interface was a single +function that took many parameters and handled both initialization segments and +media segments. Initialization segments were indicated by null segment times. + +```js +// v2.0 +/** + * @param {ArrayBuffer} data + * @param {number} periodOffset + * @param {?number} segmentStartTime + * @param {?number} segmentEndTime + * @param {boolean} useRelativeCueTimestamps Only used by the VTT parser + * @return {!Array.} + */ +function MyTextParser(data, periodOffset, segmentStartTime, segmentEndTime) { + if (segmentStartTime == null) { + checkInitSegmentOrThrow(data); + return []; + } + + var cues = []; + var parserState = new MyInternalParser(data); + while (parserState.more()) { + cues.push(parserState.nextCueOrThrow(periodOffset)); + } + return cues; +} +``` + +In Shaka v2.1, the text parser interface is now a constructor. The interface +now has explicit methods for init segments and media segments, and parameters +related to time offsets have been grouped together into one `TimeContext` +parameter. + +```js +// v2.1 +/** @constructor */ +function MyTextParser() {} + +/** @param {!ArrayBuffer} data */ +MyTextParser.prototype.parseInit = function(data) { + checkInitSegmentOrThrow(data); +}; + +/** + * @param {!ArrayBuffer} data + * @param {shakaExtern.TextParser.TimeContext} timeContext + * @return {!Array.} + */ +MyTextParser.prototype.parseMedia = function(data, timeContext) { + var cues = []; + var parserState = new MyInternalParser(data); + while (parserState.more()) { + cues.push(parserState.nextCueOrThrow(timeContext.periodStart)); + } + return cues; +}; +``` + +For more information, see the {@link shakaExtern.TextParser.TimeContext} and +{@link shakaExtern.TextParser} definitions in the API docs. + + +#### Manifest parser plugin changes + +Manifest parsers also have a new interface. The old interface had a `start()` +method that took many parameters. + +```js +// v2.0 +/** @constructor */ +function MyManifestParser() {} + +/** @param {shakaExtern.ManifestConfiguration} config */ +MyManifestParser.configure = function(config) { + this.config_ = config; +}; + +/** + * @param {string} uri + * @param {!shaka.net.NetworkingEngine} networkingEngine + * @param {function(shakaExtern.Period)} filterPeriod + * @param {function(!shaka.util.Error)} onError + * @param {function(!Event)} onEvent + * @return {!Promise.} +*/ +MyManifestParser.prototype.start = + function(networkingEngine, filterPeriod, onError, onEvent) { + this.networkingEngine_ = networkingEngine; + this.filterPeriod_ = filterPeriod; + this.onError_ = onError; + this.onEvent_ = onEvent; + + var type = shaka.net.NetworkingEngine.RequestType.MANIFEST; + var request = shaka.net.NetworkingEngine.makeRequest( + [uri], this.config_.retryParameters); + return this.networkingEngine_.request(type, request).then(function(response) { + this.manifest_ = this.parseInternal_(response.data); + this.updateInterval_ = setInterval(this.updateManifest_.bind(this), 5000); + return this.manifest_; + }); +}; + +/** @return {!Promise} */ +MyManifestParser.prototype.stop = function() { + clearInterval(this.updateInterval_); + return Promise.resolve(); +}; +``` + +In Shaka v2.1, the parameters to `start()`, which were all tied back to the +`Player` object, have been grouped into a one `PlayerInterface` parameter. +This will allow us to add features to the interface without breaking plugins. + +```js +// v2.1 +/** + * @param {string} uri The URI of the manifest. + * @param {shakaExtern.ManifestParser.PlayerInterface} playerInterface Contains + * the interface to the Player. + * @return {!Promise.} + */ +MyManifestParser.prototype.start = function(uri, playerInterface) { + this.playerInterface_ = playerInterface; + + var type = shaka.net.NetworkingEngine.RequestType.MANIFEST; + var request = shaka.net.NetworkingEngine.makeRequest( + [uri], this.config_.retryParameters); + return this.playerInterface_.networkingEngine.request(type, request).then( + function(response) { + this.manifest_ = this.parseInternal_(response.data); + this.updateInterval_ = setInterval(this.updateManifest_.bind(this), 5000); + return this.manifest_; + }); +}; +``` + +Shaka v2.1 also adds two new methods to the manifest parser interface: +`update()` and `onExpirationUpdated()`. + +The `update()` method allows `StreamingEngine` to ask for an explicit manifest +update. This is used, for example, to support `emsg` boxes in MP4 content, +which can be used by the stream to indicate that a manifest update is needed. + +```js +// v2.1 +MyManifestParser.prototype.update = function() { + // Trigger an update now! + this.updateManifest_(); +}; +``` + +The `onExpirationUpdated` method is optional. It is used by `DrmEngine` to +inform the manifest parser that the expiration time of an EME session has +changed. We use this internally in our offline support, so that we can keep +track of expiring licenses for stored content. + +```js +// v2.1 +MyManifestParser.prototype.onExpirationUpdated = + function(sessionId, expiration) { + var oldExpiration = this.database_.getExpiration(this.contentId_); + expiration = Math.min(expiration, oldExpiration); + this.database_.setExpiration(this.contentId_, expiration); +}; +``` + +For more information, see the {@link shakaExtern.ManifestParser.PlayerInterface} +and {@link shakaExtern.ManifestParser} definitions in the API docs. diff --git a/docs/tutorials/upgrade.md b/docs/tutorials/upgrade.md index 30b0423567..f14d950b96 100644 --- a/docs/tutorials/upgrade.md +++ b/docs/tutorials/upgrade.md @@ -1,727 +1,5 @@ -# Shaka v2 Upgrade Guide +# Shaka Player Upgrade Guide -This is a detailed guide for upgrading from Shaka Player v1 to v2. It is a bit -long to read from beginning to end, so feel free to skim or to search for the -class and method names you are using in your application. +If you are upgrading from v1.x, please see {@tutorial upgrade-v1}. - -#### What's New in v2? - -Shaka v2 has several improvements over v1, including: - - Support for multiple DASH Periods - - Support for DASH Location elements - - Support for DASH UTCTiming elements for clock synchronization - - Lower-latency startup - - Simplified API - - Better browser compatibility - - More detailed browser support test - - Numerical error code system - - Clears old data from the buffer to conserve memory - - Buffering state is independent of play/pause - - Distinguishes between subtitle and caption tracks - - Separate audio & text language preferences - - New plugin and build system to extend Shaka - - Cache-friendly networking - - Simpler, mobile-friendly demo app - - -#### Shaka Plugins - -Shaka v2 has a new, cleaner architecture than v1 based on plugins. In v2, -networking, manifest parsing, and subtitle/caption parsing are all plugins. - -We bundle some default plugins (HTTP support, DASH support, and WebVTT), and -we plan to expand this list in future releases. Application developers can -write their own plugins as well. Plugins can either be compiled into the -library, or they can live outside the library in the application. Application -developers can also customize the build to exclude any default plugins they -don't need. - -For a more in-depth discussion of plugins, check out {@tutorial plugins}. - - -#### Namespace - -In v1, the `Player` class was namespaced as `shaka.player.Player`. In v2, this -has been simplified to `shaka.Player`. - - -#### load() - -Before, you needed a `DashVideoSource` or other `IVideoSource` subclass to pass -to `player.load()`. The video source was constructed with a manifest URL: - -```js -// v1: -var player = new shaka.player.Player(video); -var videoSource = new shaka.player.DashVideoSource(manifestUri); -player.load(videoSource); -``` - -In v2, the entire video source concept is gone from the API. Now, you pass the -URL directly to the player, and it decides which manifest parser plugin to use -based on the file extension or MIME type: - -```js -// v2: -var player = new shaka.Player(video); -player.load(manifestUri); -``` - - -#### ContentProtection callbacks - -Shaka v1's `DashVideoSource` had a parameter for a `ContentProtection` callback. -This callback was required to play protected content because Shaka did not -interpret `ContentProtection` elements in the DASH manifest and could not -derive the license server URI automatically: - -```js -// v1: -function interpretContentProtection(schemeIdUri, contentProtectionElement) { - if (schemeIdUri.toLowerCase() == - 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed') { - // This is the UUID which represents Widevine. - return [{ - 'keySystem': 'com.widevine.alpha', - 'licenseServerUrl': '//widevine-proxy.appspot.com/proxy' - }]; - } else if (schemeIdUri.toLowerCase() == - 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95') { - // This is the UUID which represents PlayReady. - return [{ - 'keySystem': 'com.microsoft.playready', - 'licenseServerUrl': '//playready.directtaps.net/pr/svc/rightsmanager.asmx' - }]; - } else { - return null; - } -} -var player = new shaka.player.Player(video); -var videoSource = new shaka.player.DashVideoSource( - manifestUri, interpretContentProtection); -player.load(videoSource); -``` - -In v2, these callbacks are *only* required for *non-standard* ContentProtection -schemes, such as that used by YouTube's demo assets. For the 99% of you who are -using standard schemes, no callback is required. Simply `configure()` the -player with your license servers: - -```js -// v2: -var player = new shaka.Player(video); -player.configure({ - drm: { - servers: { - 'com.widevine.alpha': '//widevine-proxy.appspot.com/proxy' - 'com.microsoft.playready': '//playready.directtaps.net/pr/svc/rightsmanager.asmx' - } - } -}); -player.load(manifestUri); -``` - -For a more in-depth discussion of DRM configuration, see {@tutorial drm-config}. - -If you need to support a custom ContentProtection scheme, you can still do so -with a callback set through `player.configure()`: - -```js -// v2: -function interpretContentProtection(contentProtectionElement) { - if (contentProtectionElement.getAttribute('schemeIdUri') == - 'http://youtube.com/drm/2012/10/10') { - var configs = []; - for (....) { - configs.push({ - 'keySystem': keySystem, - // WATCH OUT: now called URI not URL - 'licenseServerUri': licenseServerUri - }); - } - return configs; - } -} - -var player = new shaka.Player(video); -player.configure({ - manifest: { - dash: { - customScheme: interpretContentProtection - } - } -}); -player.load(manifestUri); -``` - -For more on what you can specify for a custom scheme, see the docs for -{@link shakaExtern.DrmInfo}. - - -#### Detailed DrmInfo - -Shaka v1's ContentProtection callbacks could return a detailed DrmInfo object -with lots of EME-related and license-request-related settings: - -```js -// v1: -function interpretContentProtection(schemeIdUri, contentProtectionElement) { - if (schemeIdUri.toLowerCase() == - 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed') { - return [{ - 'keySystem': 'com.widevine.alpha', - 'licenseServerUrl': '//widevine-proxy.appspot.com/proxy', - - 'distinctiveIdentifierRequired': true, - 'persistentStateRequired': false, - 'serverCertificate': certificateUint8Array, - 'audioRobustness': 'HW_SECURE_ALL', - 'videoRobustness': 'HW_SECURE_ALL', - 'initData': { 'initDataType': 'cenc', 'initData': initDataUint8Array }, - - 'licensePreProcessor': licensePreProcessor, - 'licensePostProcessor': licensePostProcessor, - 'withCredentials': true - }]; - } else { - return null; - } -} -function licensePreProcessor(requestInfo) { - // Only called for license requests. - - // Wrap the body, which is an ArrayBuffer: - var newBody = wrapLicenseRequest(requestInfo.body); - requestInfo.body = newBody; - - // Add a header: - requestInfo.headers['foo'] = 'bar'; -} -function licensePostProcessor(license) { - // Only called for license responses. - // Unwrap the license, which is a Uint8Array, use/store the extra data: - var rawLicense = unwrapLicense(license); - // Now return the raw license, which is also a Uint8Array: - return rawLicense; -} -``` - -In v2, the EME settings have moved to the `drm.advanced` field of the config -object: - -```js -// v2: -player.configure({ - drm: { - advanced: { - 'com.widevine.alpha': { - distinctiveIdentifierRequired: true, - persistentStateRequired: true, - serverCertificate: certificateUint8Array, - audioRobustness: 'HW_SECURE_ALL', - videoRobustness: 'HW_SECURE_ALL', - // NOTE: initData is now an array of one or more overrides: - initData: [{ initDataType: 'cenc', initData: initDataUint8Array }] - } - } - } -}); -``` - -For a discussion of advanced DRM configuration, see {@tutorial drm-config}. - -Shaka v1's license-request-releated settings have moved to v2's network filters. - -Network filters are a generic filtering system for all networking, including -license requests and responses. They are more general and flexible, so they -take slightly more effort than the old preprocessor/postprocessor system. -However, in v2, you only need to filter your license traffic for two reasons: - - if you use cross-site credentials (v1's "withCredentials" flag) - - if you wrap/unwrap license requests and responses into some other format - -```js -// v2: -player.getNetworkingEngine().registerRequestFilter(licensePreProcessor); -player.getNetworkingEngine().registerResponseFilter(licensePostProcessor); - -function licensePreProcessor(type, request) { - // A generic filter for all requests, so filter on type LICENSE: - if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) return; - - // Equivalent to v1's 'withCredentials': true - request.allowCrossSiteCredentials = true; - - // Wrap the request data, which is an ArrayBuffer: - var newData = wrapLicenseRequest(request.data); - request.data = newData; - - // Add a header: - request.headers['foo'] = 'bar'; -} -function licensePostProcessor(type, response) { - // A generic filter for all responses, so filter on type LICENSE: - if (type != shaka.net.NetworkingEngine.RequestType.LICENSE) return; - - // Unwrap the response, which is an ArrayBuffer, use/store the extra data: - var rawLicense = unwrapLicense(response.data); - // Instead of returning the raw license, store it back to the response: - response.data = rawLicense; - // Return nothing. -} -``` - -For more on request filters, see the docs for -{@link shaka.net.NetworkingEngine.RequestFilter}, {@link shakaExtern.Request}, -{@link shaka.net.NetworkingEngine.ResponseFilter}, {@link shakaExtern.Response}. - - -#### ClearKey configuration - -Shaka v1's `ContentProtection` callbacks could be used for ClearKey, but it -required you to craft both a data URI and fake init data in the correct format: - -```js -// v1: -function interpretContentProtection(schemeIdUri, contentProtectionElement) { - var keyid; // as Uint8Array - var key; // as Uint8Array - var keyObj = { - kty: 'oct', - kid: Uint8ArrayUtils.toBase64(keyid, false), - k: Uint8ArrayUtils.toBase64(key, false) - }; - var jwkSet = {keys: [keyObj]}; - var license = JSON.stringify(jwkSet); - var initData = { - 'initData': keyid, - 'initDataType': 'webm' - }; - var licenseServerUrl = 'data:application/json;base64,' + - window.btoa(license); - return [{ - 'keySystem': 'org.w3.clearkey', - 'licenseServerUrl': licenseServerUrl, - 'initData': initData - }]; -} -``` - -In v2, this has been simplified through `player.configure()`: - -```js -// v2: -player.configure({ - drm: { - clearKeys: { - 'deadbeefdeadbeefdeadbeefdeadbeef': '18675309186753091867530918675309', - '02030507011013017019023029031037': '03050701302303204201080425098033' - } - } -}); -``` - -For more on ClearKey setup, see {@tutorial drm-config}. - - -#### BandwidthEstimator and AbrManager - -Shaka v1's `DashVideoSource` had parameters for applications to inject custom -BandwidthEstimator and AbrManager implementations. We even recommended -injecting BandwithEstimator to get persisted estimates across playbacks: - -```js -// v1: -var player = new shaka.player.Player(video); -var bandwidthEstimator = new shaka.util.EWMABandwidthEstimator(); -var abrManager = new shaka.media.SimpleAbrManager(); -var videoSource = new shaka.player.DashVideoSource( - manifestUri, /* interpretContentProtection */ null, estimator, abrManager); -player.load(videoSource); -``` - -In v2, we rolled the BandwidthEstimator concept into AbrManager. It is no -longer necessary to inject an instance to persist estimates across playbacks, -and custom AbrManagers are now provided via `player.configure()`: - -```js -// v2: -var player = new shaka.Player(video); -var customAbrManager = new MyCustomAbrManager(); -player.configure({ - abr: { - manager: customAbrManager - } -}); -player.load(manifestUri); -``` - -For more on the AbrManager interface, see the docs for -{@link shakaExtern.AbrManager}. - - -#### Selecting tracks - -Shaka v1 had separate methods for each type of tracks: `getVideoTracks()`, -`getAudioTracks()`, and `getTextTracks()`, as well as `selectVideoTrack()`, -`selectAudioTrack()`, and `selectTextTrack()`. Tracks were selected by ID. - -```js -// v1: -var videoTracks = player.getVideoTracks(); -var i = /* choose an index somehow */; -player.selectVideoTrack(videoTracks[i].id); // id, specifically video -``` - -In Shaka v2, audio and video tracks are combined into a variant track. It is -not possible to select individual audio/video streams, you can only select a -specific variant as specified by the manifest. - -You can get the currently available tracks using `getVariantTracks()` and -`getTextTracks()`. To switch tracks, use `selectVariantTrack()` and -`selectTextTrack()`, passing in the whole track object. - -```js -// v2: -var variantTracks = player.getVariantTracks(); -var i = /* choose an index somehow */; -player.selectVariantTrack(variantTracks[i]); // whole track -``` - -In v1, you could show or hide text tracks with `player.enableTextTrack()`: - -```js -// v1: -player.enableTextTrack(true); -``` - -In v2, this becomes `player.setTextTrackVisibility()`: - -```js -// v2: -player.setTextTrackVisibility(true); -``` - -See also the {@link shakaExtern.Track} structure which is used for all track -types (variant and text). - - -#### Side-loading captions/subtitles - -In Shaka v1, you could side-load subtitles that were not present in the manifest -by calling `addExternalCaptions()` on the `DashVideoSource` before `load()`: - -```js -// v1: -var player = new shaka.player.Player(video); -var videoSource = new shaka.player.DashVideoSource(manifestUri); -videoSource.addExternalCaptions(textStreamUri, 'fr-CA', 'text/vtt'); -player.load(videoSource); -``` - -In v2, this is done on with `player.addTextTrack()` after load() is complete: - -```js -// v2: -var player = new shaka.Player(video); -player.load(manifestUri).then(function() { - player.addTextTrack(textStreamUri, 'fr-CA', 'caption', 'text/vtt'); -}); -``` - - -#### Playback start time - -Shaka v1's `player.setPlaybackStartTime()` would let you start playback at an -arbitrary timestamp. It had to be called before load(): - -```js -// v1: -player.setPlaybackStartTime(123.45); -player.load(manifestUri); -``` - -In v2, this is done through an optional parameter on `load()`: - -```js -// v2: -player.load(manifestUri, 123.45); -``` - - -#### Trick play - -Shaka v1 had `player.setPlaybackRate()` that could be used for trick play by -emulating negative rate support in `video.playbackRate`. If you used -v1's `setPlaybackRate()` for trick play, use v2's `player.trickPlay()`. For -other purposes, use `video.playbackRate` directly. - - -#### configure() - -Shaka v1 and v2 both have a `player.configure()` method. Here is a map of -settings in v1 and their equivalents in v2 (most of which are at a different -level of the configuration hierarchy): - - - `enableAdaptation` => `abr.enabled` - - `streamBufferSize` => `streaming.bufferingGoal` - - `licenseRequestTimeout` => `drm.retryParameters.timeout` - - `mpdRequestTimeout` => `manifest.retryParameters.timeout` - - `segmentRequestTimeout` => `streaming.retryParameters.timeout` - - `preferredLanguage` => split into `preferredAudioLanguage` and - `preferredTextLanguage` - - `restrictions` => (same name, see below) - - `liveStreamEndTimeout` => (not needed in v2) - - `disableCacheBustingEvenThoughItMayAffectBandwidthEstimation` => - (not needed, always cache-friendly) - -The `shaka.player.Restriction` type was replaced by a simple record type. So -instead of constructing an object, simply create an anonymous JavaScript object. -`minPixels`/`maxPixels` were added to limit total pixels. Also `minBandwidth` -and `maxBandwidth` were split into `minAudioBandwidth`, `maxAudioBandwidth`, -`minVideoBandwidth`, and `maxVideoBandwidth`, see -{@link shakaExtern.Restrictions}. - -For more information on configuration in v2, see {@tutorial config}, -{@tutorial network-and-buffering-config}, and {@tutorial drm-config}. - - -#### getStats() - -Shaka v1 had `player.getStats()`. Shaka v2 has a similar method, but it returns -a somewhat different structure. - -```js -// v1: -player.getStats() - -=> Object - streamStats: StreamStats // refers to currently selected video stream - videoWidth: number // pixels - videoHeight: number // pixels - videoMimeType: string - videoBandwidth: number // bits/sec - decodedFrames: number - droppedFrames: number - estimatedBandwidth: number // bits/sec - playTime: number // seconds - bufferingTime: number // seconds - playbackLatency: number // seconds - bufferingHistory: Array // of timestamps when we started buffering - bandwidthHistory: Array of Objects - timestamp: number // seconds, when bandwidth estimate was made - value: number // bandwidth estimate, bits/sec - streamHistory: Array of Objects - timestamp: number // seconds, when video stream changed - value: StreamStats // information about the selected stream, video only -``` - -Shaka v2 does not expose playback latency or a history of bandwidth estimates. -v2's `switchHistory` is more general than v1's `streamHistory`, and covers all -stream types: - -```js -// v2: -player.getStats() - -=> Object - width: number // pixels, current video track - height: number // pixels, current video track - streamBandwidth: number // bits/sec, total for all current streams - decodedFrames: number // same as v1 - droppedFrames: number // same as v1 - estimatedBandwidth: number // bits/sec, same as v1 - loadLatency: number, // seconds between load() and the video's 'loadend' event - playTime: number // seconds, same as v1 - bufferingTime: number // seconds, same as v1 - switchHistory: Array of Objects // replaces v1's streamHistory - timestamp: number // seconds, when the stream was selected - id: number // stream ID - type: string // 'variant' or 'text' - fromAdaptation: boolean // distinguishes between ABR and manual choices - stateChange: Array of Objects - timestamp: number // seconds, when the state changed - state: string // 'buffering', 'playing', 'paused', or 'ended' - duration: number // seconds in this state -``` - -For more on stats in v2, see {@link shakaExtern.Stats}. - - -#### Player events - -In both Shaka v1 and v2, `Player` emits events. Here is a map of events and -their properties in v1 and their equivalents in v2: - - - `error` => `error` - - `detail` (various types) => `detail` ({@link shaka.util.Error}) - - `type` (vague string) => `category` - (number, unambiguous {@link shaka.util.Error.Category error category}) - - `message` (vague string) => `code` - (number, unambiguous {@link shaka.util.Error.Code error code}) - - `bufferingStart` and `bufferingEnd` => combined into `buffering` - - (no v1 equivalent) => `buffering` - (boolean, true when we enter buffering state, false when we leave) - - (no v1 equivalent) => - `{@link shaka.Player.TextTrackVisibilityEvent texttrackvisibility}` - - `trackschanged` => `trackschanged` - - `seekrangechanged` => - (no v2 equivalent, use `{@link player.seekRange}()` when updating the UI) - - `adaptation` => `adaptation` - - `contentType` (string) => (no v2 equivalent) - - `size` (width:number, height:number) => (no v2 equivalent) - - `bandwidth` (number) => (no v2 equivalent) - -For more information on events, see the Events section of {@link shaka.Player}. - - -#### Browser support testing - -In Shaka v1, you could check if a browser was supported or not using -`shaka.player.Player.isBrowserSupported()`: - -```js -// v1: -if (!shaka.player.Player.isBrowserSupported()) { - // Show an error and abort. -} -``` - -In v2, the same method exists to detect support. For diagnostics there is a new -method that will get more detailed information about the browser. This will -involve making a number of queries to EME which may result in user prompts, -so it is suggested this only be used for diagnostics: - -```js -// v2: -if (!shaka.Player.isBrowserSupported()) { - // Show an error and abort. -} else { - // Only call this method if the browser is supported. - shaka.Player.probeSupport().then(function(support) { - // The check is asynchronous because the EME API is asynchronous. - // The support object contains much more information about what the browser - // offers, if you need it. For example, if you require both Widevine and - // WebM/VP9: - if (!support.drm['com.widevine.alpha'] || - !support.media['video/webm; codecs="vp9"']) { - // Show an error and abort. - } - }); -} -``` - -For more on the support object, check out {@link shakaExtern.SupportType}. -You can also see the full `probeSupport()` report for your browser at: -{@link http://shaka-player-demo.appspot.com/support.html} - - -#### HttpVideoSource - -There is no equivalent to v1's `HttpVideoSource`. Shaka v2 only supports -playback via MSE. This is much simpler and allowed us to remove many special -cases from the code. - - -#### Offline storage - -In v1, to store offline content you used an `OfflineVideoSource`. - -```js -// v1: -function chooseStreams() { - return Promise.resolve(trackIds); -} - -function onProgress(evt) { - console.log('Stored ' + evt.detail + '%'); -} - -var videoSource = new shaka.player.OfflineVideoSource(null, null); -videoSource.addEventListener('progress', onProgress); - -var promise = videoSource.store('https://url', 'en-US', null, chooseStreams); -promise.then(function(groupId) { - console.log('Stored at group ID ' + groupId); -}); -``` - -In v2 you use the `Storage` class to store offline content. - -```js -// v2: -function chooseTracks(allTracks) { - return filteredTracks; -} - -function onProgress(storedContent, percent) { - console.log('Stored ' + percent + '%'); -} - -var storage = new shaka.offline.Storage(player); -// Optional -storage.configure({ - trackSelectionCallback: chooseTracks, - progressCallback: onProgress -}); - -var promise = storage.store('https://url', {extra: 'data'}); -promise.then(function(storedContent) { - console.log('Can be loaded using url: ' + storedContent.offlineUri); -}); -``` - -#### Offline playback - -In v1, you also used `OfflineVideoSource` to load the stored content. - -```js -// v1: -var videoSource = new shaka.player.OfflineVideoSource(groupId, null); -player.load(videoSource); -``` - -In v2, you don't have to use any special types so long as you know the URL to -use. When storing the content, you get a special URL that is simply passed to -`load` like any other URL. You can also get the info by listing all stored -content. - -```js -// v2: -player.load(offlineUri); -``` - -#### Offline listing and deleting - -In v1, you used `OfflineVideoSource` to list and delete content. - -```js -// v1: -var videoSource = new shaka.player.OfflineVideoSource(null, null); - -videoSource.retrieveGroupIds().then(function(groupIds) { - console.log(groupIds[0]); -}); - -var sourceToDelete = new shaka.player.OfflineVideoSource(groupId, null); -sourceToDelete.deleteGroup().then(function() { - console.log('Done'); -}); -``` - -In v2, you use `Storage` to list and delete stored content. - -```js -// v2: -var storage = new shaka.offline.Storage(player); - -storage.list().then(function(storedContents) { - var firstInfo = storedContents[0]; - var url = firstInfo.offlineUri; - player.load(url); -}); - -storage.delete(storedContent).then(function() { - console.log('Done'); -}); -``` +If you are upgrading from v2.0, please see {@tutorial upgrade-v2-0}. diff --git a/externs/shaka/text.js b/externs/shaka/text.js index 6b197ff3e7..c88fe2ac46 100644 --- a/externs/shaka/text.js +++ b/externs/shaka/text.js @@ -33,9 +33,9 @@ shakaExtern.TextParser = function() {}; * A collection of time offsets used to adjust text cue times. * * @typedef {{ - * periodStart : number, - * segmentStart : number, - * segmentEnd : number + * periodStart : number, + * segmentStart : number, + * segmentEnd : number * }} * * @property {number} periodStart @@ -59,8 +59,7 @@ shakaExtern.TextParser.TimeContext; * * @exportDoc */ - -shakaExtern.TextParser.prototype.parseInit; +shakaExtern.TextParser.prototype.parseInit = function(data) {}; /** @@ -75,7 +74,7 @@ shakaExtern.TextParser.prototype.parseInit; * * @exportDoc */ -shakaExtern.TextParser.prototype.parseMedia; +shakaExtern.TextParser.prototype.parseMedia = function(data, timeContext) {}; /** diff --git a/third_party/jsdoc/templates/default/tmpl/tutorial.tmpl b/third_party/jsdoc/templates/default/tmpl/tutorial.tmpl index 9dfe909977..382ea0fb44 100644 --- a/third_party/jsdoc/templates/default/tmpl/tutorial.tmpl +++ b/third_party/jsdoc/templates/default/tmpl/tutorial.tmpl @@ -1,15 +1,5 @@
- 0) { ?> -
-
    -
  • -
-
- -