-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Preload API #880
Comments
API ideas, copied from discussion in #961 (comment) We will have to change the API so that a video element is not required in the Player constructor. MediaSource would be set up later. Examples: // current flow
player = new shaka.Player(video); // attach right away, set up MediaSource
player.load('foo.mpd'); // preload flow
player = new shaka.Player();
player.preload('foo.mpd'); // start loading content
player.attach(video); // now set up MediaSource and complete the pipeline // wait to set up MediaSource, without preloading
player = new shaka.Player();
// time passes...
player.attach(video); // MediaSource setup happens now, when you want it to The |
This is a feature we anticipate wanting to provide to our clients. And more than that, I expect we'd want to preload more than just one piece of content. This is basically to support rapid channel switching behaviour, and the like. There's another wrinkle: I also anticipate we'd want to preload some piece of content and then provide it to a specific If I'm told we need this feature, I think I could work on a PR. But given the above I'd like your input. |
Idea, perhaps total brain fart:
|
I'd really like to be able to pre load segments or ranges from a single manifest player = new shaka.Player(video);
// preload with optional ranges param
player.load('foo.mpd', [
[0, 1234], // time range in ms
[2345, 3456],
[4567, 5678],
6789, // time index of single segment to pre cache
]);
// party 🎉
player.attach(video) |
Another thought about this: it would be nice if |
Hi @chrisfillmore, @bennypowers, Since #1087, a video element is no longer required in the constructor, but it must be attached by the time For preload, I am thinking along the lines of what Chris suggested in #880 (comment), where we return a token that can be used to complete the load process. The player would be capable of starting multiple preloads in parallel, to support speculation about what the end-user will click on. When the user finally clicks on a thing, you can then choose which preload to continue with. The others would then be invalidated. Maybe something like this: let player = new shaka.Player();
let token1 = player.preload('foo1.mpd');
let token2 = player.preload('foo2.mpd');
let token3 = player.preload('foo3.mpd');
// time passes...
player.attach(video);
await token3.load(); // like player.load('foo3.mpd'), but we already fetched segments
// tokens 1, 2, and 3 are all now invalidated.
// buffered data in tokens 1 and 2 has been dropped.
// buffered data in token3 has been transferred to Player and MediaSource. As for Benny's suggestion in #880 (comment), Shaka Player does not buffer multiple independent ranges in general, so I don't plan to incorporate this into the preload design. Thanks! |
@joeyparrish that looks pretty good, the only suggestion I have is that it would be preferable if the client could explicitly call something like |
I can see how that might be useful for a TV-like scenario: you could keep the next and previous channel preloaded so the user could "channel surf" with low latency. But, I wasn't thinking of how this would interact with live streams. I was imagining that we could buffer up to It's not undoable, but I had envisioned something much smaller and simpler. We'll need to give this some careful thought. What would your expectation be for having several tokens representing live streams? What should they be doing while they wait for a call to load()? |
Hello @OrenMe , Thank you for your feedback!
Let us know if you have further questions or thoughts! Thank you! |
Also, we would be happy to discuss implementation details if anyone is interested in implementing this and create a PR for it. Thanks! |
any idea more less when that feature will availible? |
Nobody is working on the implementation at the moment, but contributions are welcome. Let us know if you'd like to take on the implementation, or if you have any questions about the design we published. Thanks! |
I have no idea to how to implement. :( |
You probably couldn't find any info on that because it wouldn't quite work like that. Preload would be a Shaka Player feature loading media into memory and keeping it buffered there without connecting to MediaSource SourceBuffers. It wouldn't use Cache or ServiceWorker to fetch or store the media. This is our design doc: https://github.com/shaka-project/shaka-player/blob/main/docs/design/preload.md No worries if you don't want to tackle this yourself. We will get there eventually, but the team is prioritizing HLS improvements right now. |
Hi everyone, As it appears that this feature request is not currently being worked on, and, considering it's a pretty valuable and anticipated one, I've conducted some investigations and I'd like to share my ideas (an alternate design proposal) with you on how I think we could go about adding this feature to the shaka player. I would appreciate your feedback. ProposalThe current design document suggest as a first step to extract a part of the load graph in a loader class but I believe an easier first step would be to enable single manifest preloading with a player instance. I believe this is better approach because it's more "accessible" (less code refactor needed) and it will enable preloading single manifest already which, imo, is a valuable feature the shaka player community can start benefiting from. As next steps, we should make critical components reusable and then introduce the loader class to make multiple manifest preloading in parallel possible. In the following sections I will present the steps in more details. 1. Enable single manifest preloading with a player instanceThis step can be broken down into two substeps: (a) enable "detached" initialization of the player and (b) add preload capability. 1.a Enable "detached" player initializationThis means being able to load the player without video element: So that the following will be possible: const player = new shaka.Player();
// Init/load the player in detached mode i.e.
// Detach -> Media Source -> Parser -> Manifest -> DRM
// Note that the first two step can be done in the constructor already.
player.load(manifestUri);
// Starts streaming when a media element gets attached
player.attach(mediaElement); The player will remain backwards compatible with the current API const player = new shaka.Player(mediaElement);
// Will init/load the player and start streaming right away
// Detach -> Media Source -> Parser -> Manifest -> DRM -> Attach -> Load
player.load(manifestUri); To make this possible, we will need to remove the media element dependency for the media source engine construction ( I've been experimenting with the above and currently have a working PoC for this substep. I'd be happy to submit a draft PR for feedback. 1.b Add preload capabilityNow we can do the following: const player = new shaka.Player();
player.load(manifestUri) which will walk the load graph like this: // Manages fetched media data:
// - Keep them in memory while preloading (MS not attached)
// - Push data to source buffer once a MS engine is provided.
// - Provide interface to manage buffered/staged data
const BufferSink = class {
// Each buffer sink manage one type of content.
constructor(contentType: shaka.util.ManifestParserUtils.ContentType) {
this.buffer_ = ... // Some adequate data structure to store data + append context
this.mediaSourceEngine_ = null;
}
// Before media source engine is provided, keep data in memory
setMediaSourceEngine(mediaSource: shaka.media.MediaSourceEngine) {
this.mediaSourceEngine_ = mediaSource;
// We have the MSE now, process buffer data i.e. pipeline data to MSE
this.bufferWorkLoop_(); // async, similar to operation queue work in MSE
}
// Keep data in memory if not attached to the MS engine else pass down data
// to the MS engine
async appendBuffer(/** same args list as MS engine*/) {
this.buffer_.push(...);
// ...
}
} Then, in the streaming engine we can have a buffer sink per media state so that after fetch the data will be appended by calling 3. Make resources as reusable as possibleNow that we can preload single manifest, the next step towards preloading multiple manifest in parallel is to make resources as reusable as possible. Among reusable resources I can identify:
The above resources will be owned by the player and can be provided to the loaders (see next section) on demand e.g. via an interface. 4. Introduce loader classNow we will be ready to add loader class as described in the current design document but with the following suggestions:
Considering the above a loader class could look like this: // Pseudo code loader
shaka.Loader = class extends shaka.util.FakeEventTarget {
constructor(playerInterface, playerConfig, manifestUri, startTime, mimeType) {
...
this.playerInteface_ = playerInterface;
this.playerConfig_ = playerConfig;
this.streamingEngine_ = ...;
this.drmEngine_ = ...;
this.manifest_ = null;
// walker for the whole load graph. Will be transferred to the player
// when it's time to load
this.walker_ = new shaka.routing.Walker({
playerInterface.getNodes().detach,
playerInterface.createEmptyPayload(),
playerInterface.getWalkerImplementation()
});
}
start() {
// Walks the load graph until preload
this.walker_.startNewRoute((p) => {
return {
node: this.playerInterface.getNodes().preload,
...
};
});
// When it's time to parse the manifest
this.manifest_ = await this.playerInterface_.parseManifest(this.manifestUri_, this.mimeType_);
...
}
getWalker() {
return this.walker_;
}
getDrmEngine() {
return this.drmEngine_;
}
getManifest() {
...
}
...
};
/**
* @typedef {{
* parseManifest: function(string, string): Promise<shaka.extern.Manifest>,
* getWalkerImplementation: function():!shaka.routing.Walker.Implementation,
* ...
* }}
*/
shaka.Loader.PlayerInterface; And in the player it could be used as follow: // Pseudo code player
shaka.Player = class extends shaka.util.FakeEventTarget {
...
preload(playerConfig, manifestUri, startTime, mimeType) {
const playerInterface = {
...
};
const preloader = new shaka.Loader(playerConfig, manifestUri, startTime, mimeType, playerInterface);
preloader.start();
return preloader;
}
async load(preloader) {
// Must certainly do things before...
...
// Transfer resources
this.config_ = preloader.getConfig();
// this.applyConfig_(preloader.getConfig())?
this.drmEngine_ = preloader.getDrmEngine();
this.streaminEngine_ = preloader.getStreamingEngine();
this.walker_= preloader.getWalker();
...
// Invalide loader
async preloader.destroy();
if (preload) {
assert(this.walker_.getCurrentNode() == this.preloadNode_, 'Manifest should have been preloaded already!');
}
// Proceed with load i.e. new from "Preload" to "Load"
// Note: if we don't have a video element yet, we will wait for `attach()` to start this route.
this.walker_.startNewRoute((p) => {
return {
node: this.loadNode_,
...
}
});
// Do more things...
}
}; 5. Preload APIOnce all the above steps are done the preload API for loading multiple manifests in parallel should be fully functional and the player can the provide a shaka.Player = class extends shaka.util.FakeEventTarget {
/**
* @param {string} manifestUri The location of the manifest to preload
* @param {number=} startTime The time in the presentation timeline we should preloading from
* @param {string=} mimeType Manifest MIME type
* @param {shaka.extern.PlayerConfiguration=} playerConfig Specific player config
* @return {!shaka.Loader}
* @throws
*/
preload(manifestUri, startTime, mimeType, playerConfig) {};
} Example usage: const player = new shaka.Player();
const loader1 = player.preload('foo1.mpd');
const loader2 = player.preload('foo2.mpd', 10, undefined, {streaming: { bufferingGoal: 15 } });
// Can also be done after load() as well
player.attach(video);
player.load(loader1); Open questionsHow should we deal with player events that occur inside the loader instance during preload? It ended up being a lengthy document, I appreciate if you took the time to go through all of it. Please don't hesitate provide feedback. Thank you! |
Adds a new player method, preload. This asynchronous method creates a PreloadManager object, which will preload data for the given manifest, and which can be passed to the load method (in place of an asset URI) in order to apply that preloaded data. This will allow for lower load latency; if you can predict what asset will be loaded ahead of time (say, by preloading things the user is hovering their mouse over in a menu), you can load the manifest before the user presses the load button. Note that PreloadManagers are only meant to be used by the player instance that created them. Closes #880 Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
We should have an API to allow an application to pre-load a manifest and some of the media segments without involving
MediaSource
or a<video>
element. An application could speculatively pre-load the beginning of several pieces of content while the user is making a decision. That content could then begin playing almost instantly.This requires some redesign. Currently,
new Player()
requires anHTMLMediaElement
, and theload()
pipeline connects from manifest parsing all the way to streaming andMediaSource
. These things would need to be decoupled, and a newpreload()
method would have to be introduced.Note, though, that if we had it today, this new
preload()
method would not be usable from a service worker just yet, because our DASH manifest parser depends onDOMParser
for XML parsing, andDOMParser
is not available in a service worker.Until manifest parsing can be done from a service worker context, operations involving a manifest must be done from the page context.
The text was updated successfully, but these errors were encountered: