Description
tl;dr
Due to confusion about the nature of my request I want to clarify I am not suggesting I should be able to recycle the source list concept in HTML as a sequential playlist. I am merely suggesting I should be able to go from this:
<audio>
<source src="song1.ogg">
<source src="song1.mp3">
</audio>
to (by mutating the src
attributes):
<audio>
<source src="song2.ogg">
<source src="song3.mp3">
</audio>
And the currentSrc
should update to use one of the new src
attributes on the <source>
children.
jsfiddle showing current behavior
original posting
I was reading the <source>
element spec and came across the following while trying to troubleshoot a bug in my library:
Dynamically modifying a
source
element and its attribute when the element is already inserted in a video or audio element will have no effect. To change what is playing, just use thesrc
attribute on the media element directly, possibly making use of thecanPlayType()
method to pick from amongst available resources. Generally, manipulatingsource
elements manually after the document has been parsed is an unnecessarily complicated approach.
Respectfully - I disagree! My component allows a user to specify a sequential playlist of songs to cycle through, and a list of potential sources for each song in the playlist, to be used by the browser as the browser sees fit.
The above quotation implies I am only able to benefit from the browser's automatic source selection for the first song I load. I have to take a totally imperative approach for subsequent tracks, which is a shame in a context like React where I would like to be able to declaratively specify my source list as a function of the currently chosen song index.
What reads simpler to you:
render() {
const index = this.state.activeIndex;
const sources = this.props.sourcesByIndex[index];
return (
<audio ref={ref => this.audio = ref}>
{sources.map(source =>
<source key={source.src} src={source.src} type={source.type} />
)}
</audio>
);
}
or:
componentDidUpdate(prevProps, prevState) {
const newIndex = this.state.activeIndex;
if (newIndex !== prevState.activeIndex) {
for (const source of this.props.sourcesByIndex[newIndex]) {
const mimeType =
source.type ||
useSomeDatabaseLookupLibraryToFindMimeType(source.src);
if (this.audio.canPlayType(mimeType) {
this.audio.src = source.src;
break;
}
}
}
}
render() {
// same render implementation as above
}
Manually iterating through my source options, checking canPlayType()
for each, and updating the src
attribute seems to me an unnecessarily complicated approach! 😄
Sidenote
While reading through both the spec and MDN I came across a notably discrepancy.
Spec
(Emphasis mine)
If the
type
attribute is not specified, the user agent will not select a differentsource
element if it finds that it does not support the image format after fetching it.
The quotation refers specifically to images, but I'm assuming it applies to audio and video as well.
MDN
(Emphasis mine again)
If the
type
attribute isn't specified, the media's type is retrieved from the server and checked to see if the user agent can handle it; if it can't be rendered, the next<source>
is checked.
Which is it - implementation-wise? If the MDN version turns out to be what's really in the wild, then we're missing a critical feature - automatic mimetype retrieval - when forced to restort to the method of source updating suggested in the spec.
ETA
In my original code example I mapped this.props.sources
in the render
method - I meant to just map sources
. This is updated.