Thoughts on the TVTuner class in the source-centric API

All,

This is an attempt to summarise a couple of discussions that I've had offline with various people, prompted by the edits I've been making to the API.  The thoughts of the group are very welcome.

The question that prompted this is " in our modified API, what does the call to TVSource.tuneToChannel() return?"  While a TVTuner appears to make sense at first, there are several problems with that:
1)            You still then have to get the TVMediaStream before you can connect it to a <video> element and start playing the channel
2)            There may not be a one-to-one mapping between a TVTuner instance and a channel being played: a single hardware tuner may be able to receive more than one channel at the same time.

I thought that returning a TVMediaStream would solve this, but that then raised other questions:
1)            How do you find the set of channels currently being played by the source?  We've already agreed that one source may be used to play more than one channel at a time, but that's not reflected in the API.
2)  How can an application stop presenting a channel and free the resources being used for it?  While MediaStreamTrack.stop() could be used to do this, that's pretty counter-intuitive.  We could say that when the TVMediaStream is garbage collected then all the resources are freed, but that's not very predictable.
3)            When an app starts, if a channel is already being presented, how can the app discover that channel?  The use case here is an STB where a broadcast-related app is started and wants to resize the video that is currently being displayed (i.e. to fit it in with the app's UI).
4)            What object receives notifications for channel change events or errors, parental control events and so on?


My thinking at the moment is that the name of the TVTuner is misleading: while it implies that the class represents a physical tuner, that isn't really the case given its current API.  One option would be for this object to represent a "media session", where tuning to a channel creates a new media session which an application can register listeners on and can get a TVMediaStream from.  This would give interfaces like this:


interface TVSource : EventTarget {
    Promise<sequence<TVChannel>> getChannels();
    Promise<sequence<TVTuner>>   getSessions();
    Promise<TVTuner>             tuneToChannel(TVChannel channel, optional TVTuner tuner);
    Promise<TVTuner>             tuneTo(DOMString tuningParams, optional TVTuner tuner);
    Promise<void>                startScanning(optional TVStartScanningOptions options);
    Promise<void>                stopScanning();
    Promise<unsigned short>      getAdditionalSessions(DOMString qualityLevel);
    readonly attribute TVSourceType type;
    readonly attribute boolean      isScanning;
             attribute EventHandler onscanningstatechanged;
};


interface TVTuner : EventTarget {
    Promise<TVSource> getSource();
    Promise<void>     stop();
    readonly attribute DOMString      id;
    readonly attribute TVChannel?     currentChannel;
    readonly attribute TVMediaStream? stream;
    readonly attribute double         signalStrength;
    readonly attribute boolean        isRecordable;
             attribute EventHandler oncurrentchannelchanged;
             attribute EventHandler oneitbroadcasted;
             attribute EventHandler onemergencyalerted;
};

Note that some event handlers have moved from TVSource to TVTuner in this case.

The TVTuner object can then be re-used by an app that wants to change channel but use the same resources.  The stop() method stops presenting the current channel and frees any resources used.  The getAdditionalSessions() method lets an app determine how many more channels (of the specified quality level) can be played simultaneously from that source.

This keeps the semantics of a TVMediaStream very simple, and it's mostly used for connecting to a media element to present a channel.



Another alternative is to fully take the approach described by getUserMedia(), which does support most of what we want to do, but requires using the MediaStreamTrack interface for most of the operations.  We would either have to extend MediaDeviceInfo to introduce a getChannels() method and other TV-specific APIs and events, or define our exisitng APIs in a way that is compatible with this approach.

var source = null;

navigator.mediaDevices.enumerateDevices()
   .then(function (devicesInfo) {
     source = devicesInfo.find(function (deviceInfo) {
       // Or use "getCapabilities" to detect support for the "tvchannel"
       // constraint if kind cannot be extended?
       return deviceInfo.kind === 'tvsource';
     });
     return source.getChannels();
   })
   .then(function (channels) {})
     var channel = channels[0];
     secondChannel = channels[1];
     navigator.mediaDevices.getUserMedia({
       video: {
         deviceId: source.deviceId,
         tvchannel: channel
       }
     })
   })
   .then(function (stream) {
     tvStream = stream;

     // Render the stream
     document.getElementById('video').srcObject = tvStream;

     // Switch to another channel
     return tvStream.getVideoTracks()[0].applyConstraints({ tvchannel:
secondChannel });
   })
   .then(function () {
     // Stop stream
     tvStream.getVideoTracks()[0].stop();
   });



Thoughts and suggestions are welcome.

Steve.

Received on Monday, 5 December 2016 15:05:36 UTC