-
Notifications
You must be signed in to change notification settings - Fork 7.5k
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
Advanced Plugins for 6.0 #3690
Advanced Plugins for 6.0 #3690
Conversation
8f35177
to
a2d8686
Compare
throw new Error('illegal plugin name; must be non-empty string'); | ||
} | ||
|
||
if (plugins[name]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should also check Player.prototype[name]
I kind of like |
@dmlap You mean As for That said, what about an alternative where the plugin was still exposed as |
Fwiw, I don't think having a plugin called |
For sure. That's more of a long-term design goal, since we're still mutating the |
I think 6.0 is a good time to drop support for that. |
Naming it registerPlugin makes sense to me. 👍 The biggest question I see people run into with complex plugins is storing state/properties for just the plugin, and it'd be great if we could answer that question with this update. At the same time I would love to not lose the simplicity of the current API and the ability to create first-class functions on the player. e.g. // complex
videojs.registerPlugin('fooState', {
init: function(options){
this.foo = options.foo;
// this.player_ automatically added to instance object
this.player_.play();
},
isFoo: function(){
return this.foo;
}
});
player.fooState({ foo: true });
player.fooState.isFoo();
// simple
videojs.registerPlugin('jumpTo10', function(){
this.currentTime(10);
});
// Allowing plugin authors to do this is powerful, e.g. jQuery
player.jumpTo10(); That feels perty to me. |
@heff Thanks for the feedback. It seems we're on the same page here because what I've implemented is very similar (I think) what you described! I can see keeping the jQuery-style plugins - I'm happy to un-deprecate that. And modifying the API to be more similar to what we have now as both you and @dmlap brought up. This proposal also includes a concept of state for each plugin/player combination. Which can be understood as behaving as a very lightweight model/view implementation. It works kind of like this currently: videojs.registerPlugin('foo', {
init: function(options) {
this.foo_ = options.foo;
this.player_; // this is the player.
this.state_; // this is the state.
},
isFoo: function() {
return this.foo_;
}
});
player.plugin('foo').init({foo: true});
player.plugin('foo').isFoo(); // true
player.plugin('foo').on('statechange', function() {
alert('foo changed to ' + this.plugin('foo').isFoo() + '!');
// Maybe do some DOM manipulation or something here?
});
player.plugin('foo').state({foo: false}); // "foo changed!" That's without making any changes to what's in the PR at the moment. Like I said, the |
I've been tinkering with replacing Particularly around the player.foo(); // this === player
player.foo.isFoo(); // this === player.foo That's all well and good, but when I create a plugin like this: videojs.registerPlugin('foo', {
init: function() {},
isFoo: function() {}
}); My natural inclination would be that inside any of the functions I passed in for a "complex" plugin, So, there's a mismatch of expectations there. Either we make the |
In general, my philosophy is to avoid tricky things like this. As it stands, my new implementation does a lot of confusing things, like creating a wrapper function on the // Create a Player.prototype-level plugin wrapper that only gets called
// once per player.
plugins[name] = Player.prototype[name] = function(...firstArgs) {
// Replace this function with a new player-specific plugin wrapper.
const wrapper = function(...args) {
if (this.active_) {
this.teardown();
}
this.trigger('beforesetup');
plugin.setup(...args);
this.trigger('setup');
};
// Wrapper is bound to itself to preserve expectations.
this[name] = Fn.bind(wrapper, wrapper); |
I'm tinkering with API ideas in a JSBin. |
I think it's fine to distinguish simple vs complex plugins, where the context of a simple function is the player and the context of complex plugin functions is the plugin. That's not a hard concept to learn, and the APIs are different enough.
Ideally I think a console error logged. If people want to reset a plugin they need to call dispose first. If we try to magically support that I think it would actually lead to more confusion. |
As we discussed quasi-IRL, I think it makes a lot of sense to follow the React API (particularly ES6) for plugins. I think we were circling around something like this:
|
Yep, that's very close to what I've got in mind. I'm going to be updating this PR this week with the changes we discussed. |
7f465bf
to
0d9232b
Compare
cfb273e
to
621e3f8
Compare
At this point, Component has been reimplemented as an evented object - I wanted to make sure it would work - but that change is going to be rolled back to keep the scope of this a bit more sane. |
@@ -29,7 +29,7 @@ QUnit.test('defaults when items not provided', function(assert) { | |||
assert.equal(track.enabled, false, 'enabled defaulted to true since there is one track'); | |||
assert.equal(track.label, '', 'label defaults to empty string'); | |||
assert.equal(track.language, '', 'language defaults to empty string'); | |||
assert.ok(track.id.match(/vjs_track_\d{5}/), 'id defaults to vjs_track_GUID'); | |||
assert.ok(track.id.match(/vjs_track_\d+/), 'id defaults to vjs_track_GUID'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is repeated in a few places. The previous test assumed that the GUID was 5 digits long, which isn't a reliable assertion. Implementing the evented mixin in Component
caused each of these assertions to fail.
And the EDIT: Here is a comparison of the branch implementing the evented mixin for components. |
948b706
to
2ffb3b5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a big PR and it looks like some non-plugin related code snuck in. It would be nice to have the plugin-related changes in an isolated PR.
exampleOption: true | ||
} | ||
} | ||
const Plugin = videojs.getPlugin('Plugin'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
videojs.getPlugin('Plugin')
looks a little weird to me. How would you feel about plopping the plugin base class at videojs.Plugin
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it's a bit weird, but... so is videojs.getComponent('Component')
, no? I think it's more important that the API be consistent/predictable.
videojs.registerPlugin('examplePlugin', ExamplePlugin); | ||
``` | ||
|
||
### Setting up a Class-Based Plugin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to collapse this and the previous section since they're identical to the basic plugin examples above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that makes sense. I'll reshuffle this doc a bit to eliminate some of the duplication.
* @return {Object} | ||
* An object containing plugins that were added. | ||
*/ | ||
static registerPlugins(plugins) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to add much for end users that they couldn't achieve by repeating registerPlugin()
a couple times in a row.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Will remove this and deregisterPlugins
, which was really only added for my own convenience.
@@ -1,168 +1,220 @@ | |||
/** | |||
* @file text-track-settings.js | |||
*/ | |||
import Component from '../component'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes to this file look nice but it seems unrelated to this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it was previously rebased on another branch while I waited for that to be merged. If my other PRs for removing lodash and object.assign are merged, this will be rebased again and the changes should shrink further.
player.dispose(); | ||
}); | ||
|
||
QUnit.test('Plugin that does not exist logs an error', function(assert) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@misteroneill is this test replicated elsewhere since it's removed here? Because it was a pita to add it I remember. I have a hard time finding it or something similar in this big changeset.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like I missed the test for this feature. Nice catch, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now tested in player.test.js, under the "options: plugins" test. I've changed so it throws now instead of just logging. Failing loud in this case seems like a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thanks!
9ed7f41
to
fdf983e
Compare
4ac5e0f
to
56de0b5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Virtualisation |
Advanced/Class-based Plugins for 6.0
As part of 6.0, we want to make some improvements to plugins while still supporting the current style of plugins for the most part. There are some minor backward-incompatibilities, but nothing that's likely to break existing plugins.
Though this is technically a breaking change, the old stuff should still work!
Goals
These are the broad feature goals of this effort. The overarching approach will be to maintain simplicity throughout the implementation, avoiding assumptions and opinions whenever possible. For now, we are providing a foundation not a framework, but something both we and our users can build on easily.
Player.prototype
).VERSION
property.Specific Changes
Changes that are backward-incompatible are marked with [BI].
videojs.registerPlugin
andvideojs.registerPlugins
and deprecatevideojs.plugin
.Player.prototype
method will now throw an error.videojs.getPlugin
,videojs.getPlugins
, andvideojs.getPluginVersion
.Player#usingPlugin
to detect if a plugin has been initialized on a player.Plugin
base class for enhanced plugin functionality.mixins/evented
, which provides objects with methods fromEvents
(on
,one
,off
, andtrigger
). This uses aneventBusEl_
as an event bus because theEvents
module doesn't play very nicely with non-element objects.mixins/stateful
, which provides objects with a React-likestate
property andsetState
method - as well as triggering a"statechanged"
event if the object has atrigger
method.Requirements Checklist