-
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
feat: middleware #3788
feat: middleware #3788
Conversation
I haven't dug into the code yet, but from the description it seems odd that for What is the purpose of the |
@@ -20,9 +20,15 @@ | |||
"build": "grunt dist", | |||
"change": "grunt chg-add", | |||
"clean": "grunt clean", | |||
"grunt": "grunt", |
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.
Most of this will get removed before it's ready. Makes working with this locally a bit easier.
It'll probably get pulled into a separate PR.
} | ||
|
||
middlewareGet(method) { | ||
return this.middleware_.reduceRight(this.middlewareIterator(method), undefined); |
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.
Finally, I can use reduce
and reduceRight
!
*/ | ||
if (typeof define === 'function' && define.amd) { | ||
define('videojs', [], () => videojs); | ||
module.exports = videojs; |
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 will get pulled into a separate PR.
module.exports = videojs; | ||
} | ||
videojs.use = middlewareUse; | ||
videojs.use('video/mp4', 'videojs/html5'); |
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.
How to register the techs for native functionality is still something I'm a bit unsure about.
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.
Definitely something we need to think about. Because as it stands you can never add a middleware that handles setSource
for 'video/mp4' since there is a tech registered as the first item always.
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 great start. There are a couple things we need to work through though, like techs abruptly ending the middleware chain as talked about in slack.
Also, we might want to see if we can unify this syntax in some way so that setSource
is more like a generic setter. Although to do that you would have to make the setCurrentTime
setter async instead of sync which would likely have some bigger impacts.
}); | ||
|
||
// something weird happened, try the next middleware on the current level | ||
} else if (middleware.length > 1) { |
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.
Would probably be good to at least log a warning that middleware for this src
was undefined.
|
||
// we've succeeded, now we need to go deeper | ||
acc.push(mw); | ||
ssh(_src, middlewares[_src.type] || [], next, acc); |
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.
We need some way in this case to prevent us from calling the same middleware over and over. If the middleware leaves the src.type
unchanged and doesn't send along an error, we will keep calling the same middleware over and over.
|
||
// something happened, try the next middleware on the current level | ||
if (err) { | ||
return ssh(src, middleware.slice(1), next, acc); |
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.
Instead of doing a return
here which implies that we care about the return value, can we just put an else
on the if statement and add the following two lines to it.
This will also make the code more consistent with other recursive calls in this function which don't return.
|
||
// we've reached a fork, so, we need go deeper | ||
} else { | ||
ssh(src, middlewares[mw] || [], next, acc); |
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.
So this makes it possible to do something like videojs.use('video/foo', 'my-standard-middleware-stack')
and when that "middleware" is hit it would then start processing the my-standard-middleware-stack
set of middlewares, right? Seems interesting.
Although assuming that set of middleware eventually changes the type back to something standard we need to determine if we will ever call the same middleware twice.
const middlewares = {}; | ||
|
||
export function use(type, middleware) { | ||
(middlewares[type] = middlewares[type] || []).push(middleware); |
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'm too new to the project to know the general code style guidelines, but I find that lines like this while clever add an unnecessary obstacle to reading the code.
I feel that dividing this up into two lines makes it more idiomatic, and thus easier to read:
middlewares[type] = middlewares[type] || []
middlewares[type].push(middleware)
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.
but my one lines...
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.
:D
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.
If we're picking this nit, I generally prefer concat
to || []
.
middlewares[type] = [].concat(middlewares[type], middlware)
This is a little bit of nonsense here since we're ok with mutating the original array, so ¯_(ツ)_/¯
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.
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 also think that "Type" should be renamed to mimeType
. I am also wondering if it would make more sense for middleware to come first in the arguments order. I want to use()
hls
for x
type(s). I also thing that type should be able to be an array.
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.
@mmcc The only problem with concat
in that case is that if middlewares[type]
is undefined
, you'll get an array like [undefined, middleware]
which I think is probably undesirable.
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 went for the two line method.
} | ||
|
||
middlewareSet(method, arg) { | ||
return this.middleware_.reduce(this.middlewareIterator(method), arg); |
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.
Hooray for the right tool for the job! (reduce
here and reduceRight
below!)
|
||
middlewareGet(method) { | ||
return this.middleware_.reduceRight(this.middlewareIterator(method), undefined); | ||
} |
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 feels like these three middleware functions might belong in the middleware.js
file instead. Just be that they accept a list of middlewares to process that would be passed in.
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 methods should be private. The reason why they aren't in middleware.js
and why setSource
is special is because setSource
traverses through our middleware tree and sets up the specific middleware to be used for the player at this time. Though, I guess it can be moved into middleware.js
and then this.middleware_
could be passed in.
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.
Right, I was assuming that this.middleware_
would be passed in.
The player.js
file is big enough as it is. Seems like iterating over a list of middlewares should be in the middlewares file instead.
Should middleware be given a reference to the |
It seems like it could add some new capabilities. In fact to implement the use case I talked about before (unmute sets volume to above 0) you would need to handle the mute setter and be able to set volume on the player. This could also cause problems if someone does something silly like tries to set |
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.
Silly nitpicks aside, I really, really like this concept. A lot. Huge 👍
this.middleware_ = mws; | ||
this.loadTech_(tech.name, src_); | ||
middleware.setTech(mws, this.tech_); | ||
}); |
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.
If tech.name
is the only thing used here, we could destructure in the args. I don't feel strongly about this one, just something I've started doing a little more of lately.
middleware.setSource(src, ({ name }, src_, mws) => {
this.middleware_ = mws;
this.loadTech_(name, src_);
middleware.setTech(mws, this.tech_);
});
const middlewares = {}; | ||
|
||
export function use(type, middleware) { | ||
(middlewares[type] = middlewares[type] || []).push(middleware); |
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.
If we're picking this nit, I generally prefer concat
to || []
.
middlewares[type] = [].concat(middlewares[type], middlware)
This is a little bit of nonsense here since we're ok with mutating the original array, so ¯_(ツ)_/¯
} | ||
|
||
export function setSource(src, next) { | ||
setTimeout(()=>ssh(src, middlewares[src.type] || [], next, []), 1); |
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 thought Standard required spaces here (i.e. () => foo()
)?
I don't feel crazy strong either way, but on line 16 there are spaces so we're not even being consistent within the same file.
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.
Apparently not but yeah, I agree that there should be more whitespace. Originally added it in to verify that async works.
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 PR could also really use a tech guide update, that would certainly make it more understandable for me.
"predev": "babel src/js -d es5", | ||
"dev": "run-p babel browserify", | ||
"dev-test": "run-p dev test-watch", | ||
"babel": "babel -w src/js -d es5", |
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.
do we want to keep all of these in here? Most of them seem useful, so I wouldn't mind it.
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'm going to pull these out of this PR when getting ready to merge and make a separate PR out of it.
@@ -829,6 +834,8 @@ class Player extends Component { | |||
'techId': `${this.id()}_${techName}_api`, | |||
'videoTracks': this.videoTracks_, | |||
'textTracks': this.textTracks_, | |||
'remoteTextTracks': this.remoteTextTracks_, | |||
'remoteTextTrackEls_': this.remoteTextTrackEls_, |
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 made a change that is basically the same as this in the Unify TextTrack API PR. Does this change need to be in this PR? (if so we just need to be aware that this will have to be merged first, and the rebase could be a bit messy.)
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.
Unfortunately, this change does need to be in this PR, some of the tests rely on tracks and will fail without it because techs are now async.
@@ -920,6 +923,8 @@ class Player extends Component { | |||
// Save the current text tracks so that we can reuse the same text tracks with the next tech | |||
this.videoTracks_ = this.videoTracks(); | |||
this.textTracks_ = this.textTracks(); | |||
this.remoteTextTracks_ = this.remoteTextTracks(); |
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.
Same as the other comment with a new link to the unify texttrack api PR.
return this; | ||
} | ||
|
||
src_(source) { |
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 will need a jsdoc comment
if (this.tech_) { | ||
return this.tech_.textTracks(); | ||
if (!this.tech_) { | ||
this.textTracks_ = this.textTracks_ || new TextTrackList(); |
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.
See Other comments on Unify TextTrack API PR
if (method in middleware.allowedSetters) { | ||
return middleware.set(this.middleware_, this.tech_, method, arg); | ||
} | ||
|
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.
seems like we should check do this here, to prevent checking this.tech_
in two places below.
if (!this.tech_) {
return
}
Also it seems like this.tech_.ready()
already checks if the tech is ready? Seems like we could merge a lot of this code together to make it much simpler.
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're right, we probably want to put the middleware section into one of the ifs.
It's possible that just doing .ready() works, in which case, I can see if it's worth trying to simplify it for this PR.
@@ -1562,6 +1572,10 @@ class Player extends Component { | |||
techGet_(method) { | |||
if (this.tech_ && this.tech_.isReady_) { | |||
|
|||
if (method in middleware.allowedGetters) { |
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 function could also use some similar cleanup, maybe we can do both in another PR, but its always bothered me that it seems so messy an nested.
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.
Same here. I'd prefer to do the cleanup in a separate PR but might do it if it helps make the middleware part more clear as well.
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.
we can't actually do this cleanup here because we need to return a value from this function and this.ready()
doesn't guarantee a value, even if you're running it synchronously. However, I think I was able to simplify techCall
.
const middlewares = {}; | ||
|
||
export function use(type, middleware) { | ||
(middlewares[type] = middlewares[type] || []).push(middleware); |
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 also think that "Type" should be renamed to mimeType
. I am also wondering if it would make more sense for middleware to come first in the arguments order. I want to use()
hls
for x
type(s). I also thing that type should be able to be an array.
} | ||
|
||
export function setSource(setTimeout, src, next) { | ||
setTimeout(() => ssh(src, middlewares[src.type], next), 1); |
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.
will we get this far if a src
does not have a type
? Should we be checking for this somewhere?
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 this be async?
setTimeout(() => ssh(src, middlewares[src.type], next), 1); | ||
} | ||
|
||
export function setTech(middleware, tech) { |
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.
middlewareList since it seems like we want it to be an array here?
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.
Or middlewares
.
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.
kind of tricky since middleware is both a singular and a plural noun.
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.
Looking good. Some minor feedback/comments.
|
||
export default videojs; | ||
videojs.use = middlewareUse; |
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.
There a reason this comes after module.exports
?
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.
Nope.
@@ -9,6 +9,9 @@ | |||
<body> | |||
<div id="qunit"></div> | |||
<script src="../node_modules/qunitjs/qunit/qunit.js"></script> | |||
<script> | |||
QUnit.config.reorder = false; |
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 don't think so. I think setting reorder
to false
is really used to mask bad cleanup in tests. I think @gkatsev may have cleaned that up in another PR as well.
@@ -283,6 +281,7 @@ QUnit.test('should asynchronously fire error events during source selection', fu | |||
}); | |||
|
|||
this.clock.tick(1); | |||
this.clock.tick(1); |
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.
Could we add an explanation as to why we need to tick 2ms instead of 1ms?
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's because the error is triggered async, so, we need one for it, but we need an additional one for the player creation itself.
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 added a comment
@mmcc @boushley @misteroneill I've changed the PR from WIP to RFC. I think it's in decent shape now just needs closer inspection. |
@gkatsev It would be a good idea to run the jsdoc linter over this code |
setSourceHelper(src, mwrest, next, acc); | ||
} else { | ||
// const techs = [].concat(middlewares['*']); | ||
// const tech = techs.filter((tech) => !!tech.tech.canPlayType(src.type))[0].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 these commented lines just be removed?
Description
This is the initial PR for middleware which hopes to solve the problems from #3734.
Middleware are objects with two special functions
setSource
andsetTech
. On top of these special methods, it can have getters and setters. Currently, the only allowed ones arecurrentTime
,duration
, andsetCurrentTime
. In the future it can be extended to allow more. Also, methods likeplay
could probably be treated as setters and events originating from the video element can be treated as getters and events triggered on the player could be treated as setters. But that's more in the future.The core of middleware is that video types are basically a routing tree. So, you can "use" a middleware on a particular type but you can also "use" a tech on a particular type using special syntax since techs need a bit extra handling.
The contrib-hls object could look something like this:
I have a simple live example here: http://plnkr.co/edit/Ap9Fvg21lpYW3Rnlk9ty?p=preview