After a really intense discussion with Richard Feldman and the awesome crew at Elm Philadelphia last night, we came up with an alternative media API that’s a big departure from anything. I’ve tried before. I wanted to present it here for feedback:
The basic concept is you have an opaque type called Id
that you create with a task:
create : Source -> Config -> Task Error Id
Source
would be some kind of source, like a Url (or a list of fallback Urls). Config
would look something like this: {autoplay : Bool, muted: bool, loop: Bool}
.
Of course, media has regular state updates, such as the position in the media while it’s ended, so you would subscribe to those changes with:
state : Id -> (State -> msg) -> Sub msg)
.
State
being a record representing media state.
The state can be set with:
changeSetting: Id -> Media.Setting -> Task Error State
(It returns the new state after setting the properties)
This means that you can set your state easily in your update function:
PlayButtonClicked -> (model, Task.attempt StateUpdated (setState [play]))
For audio, that’s basically it. The AudioElement isn’t located in the visible DOM, it just plays through your speakers.
For video, we have to actually position the video image somewhere so we need one more function:
video : Media.Id -> List (Html Attribute) -> Html msg
which lets you position and style the video image in the DOM.
There’s some deeper configuration stuff to be dealt with, such as adding subtitle text tracks, and creating a stateWithOptions
subscription, but this is basic shape of the thing.
Benefits
- Much, much, much easier to keep media state in synch.
- There can be no confusion between which media element you’re attempting to play/pause/etc.
- Lets us handle a lot of the million and one edge case weird policy differences between browser media APIs as an implementation detail, automatically for users of the library.
- Maps nicely to media formats on other platforms, if/when Elm is available there. In AVFoundation on iOS, for instance, this api can work pretty much the same, just with slightly different state types.
- Better Error handling and easier to make impossible states impossible
- It really works well in the context of the rest of the media APIs. For Media Stream Capture, for instance, we just need to add a
captureSource: Config -> Source
function, and use it to create a function that we set the source of our player. In Web Audio, we can create a sourceNode like so:audioSourceNode : Id -> AudioNode
. - Substantially simplifies the eventual implementation of live streaming/adaptive bitrate.
TL;DR
The API for media playback, in broad strokes, basically looks likes this:
create : Config -> Task Error Id
changeSetting : Id -> Setting -> Task Error State
state: Id -> (State -> msg) -> Sub msg
video : Id -> List (Html Attribute) -> Html msg
EDIT: Simplifying further by putting Source
into the Config
record in create: Config -> Task Error Id
FURTHER EDIT: Renaming setState
to changeSetting