CloudSynchroniser
is a JS implementation of the client library API for the Synchronisation Service (a.k.a cloud-sync).
Version 2.0.0
- Synchronisation Timelines: A
synchronisation timeline
is a service-generated timeline based on timelines of the same type and origin (i.e. same timelineType and contentId). Clients can only subscribe to synchronisation timelines as opposed to individual timelines.TimelineInfo
- Synchronisation Timeline Election: A Synchronisation Timeline's state (timestamp) is updated by applying an election algorithm to the list of available candidate timelines. Candidate timelines WILL have the same
timelineType
andcontentId
values as the sync timeline. The algorithm selects which candidate timeline's state to use to compute the synchronisation timeline's new state. This algorithm runs dynamically and will be triggered when- timelines are registered or removed
- one of the registered timelines' state has changed significantly (beyond a threshold of 40 ms or speed has changed)
- dvbcss-clocks (Available as an NPM package)
- MQTT Client library : paho-mqtt v1.0.3 (Available as an NPM package)
- Sync Service (v2.0.0):
If you wish to build it into a single JS file suitable for the browser (i.e. for including in a webpage) then do this:
$ grunt
The resulting JS client library is placed in dist/browser/Cloud-SyncKit.js.
URN addTimelineClock(clock, timelineType, timelineId)
synchronise(clock, timelineType, contentId)
URN addTimelineSource(mediaObject, timelineType, contentId)
removeTimeline(timelineURN)
Promise<TimelineInfo[]> getAvailableTimelines()
subscribeTimeline(String timelineUrn)
disableTimelineSync(String timelineUrn)
syncClockToThisTimeline(clock, timelineURN, correlation)
Event | Description |
---|---|
DeviceRegistrationSuccess |
client successfully registered with service |
DeviceRegistrationError |
client registration failure |
WallClockAvailable |
Local WallClock synchronised to WallClock service |
WallClockUnAvailable |
Local WallClock not sync'ed to WallClock service |
SyncServiceUnavailable |
Connection to Sync Service failed |
TimelineAvailable |
A requested timeline shadow is available. Includes Timeline property which provides a dvbcss-clocks clock object as the local estimate of the timeline |
SyncTimelinesAvailable |
Array of TimelineInfo * objects representing the synchronisation timelines currently available |
SyncTimelinesUnavailable |
Deprecated |
LowSyncAccuracy |
Emitted when sync accuracy drops below threshold (specified when CloudSynchroniser object is created) |
TimelineInfo
is an object including properties for a given timeline (identified by timelineId
).
Example:
{
timelineId: "urn:123:fd24c9ef-2a7f-4622-9de2-41f7bd38fd85",
sessionId : "123",
timelineType: "tag:rd.bbc.co.uk,2015-12-08:dvb:css:timeline:simple-elapsed-time:1000",
frequency: 1000,
contentId: "https://download.blender.org/durian/trailer/sintel_trailer-720p.mp4",
channel: "Sessions/123/timelines/urn:123:fd24c9ef-2a7f-4622-9de2-41f7bd38fd85/state",
providerId: "820b504b-91f9-484f-a3e0-91a2c09e39b2",
providerType: "synccontroller",
useForSessionSync: true,
available: true,
createdOn: "2018-06-11T11:20:01.614Z"
};
Use the factory getCloudSynchroniser()
method to create a CloudSynchroniser object and register the client to the Sync Service via a known endpoint (syncUrl
parameter).
To join an existing synchronisation session, use the same sessionId
for all your clients. The deviceId
parameter identifies the client and is of type string
. It needs to be unique within the session.
The options
parameter is an object specifying preferences such as the sync mode.
// Require cloud-synchroniser module
var cloudSyncKit = require("CloudSyncKit");
var syncUrl = { hostname: "mqttbroker.edge.platform.2immerse.eu" }; // a Sync Service MQTT endpoint
var sessionId = "223dsf452asd"; // a globally-unique session identifier
var deviceId = "chris_iphone"; // a client identifier
var options = { syncTimelineElection: cloudSyncKit.SyncTLElection.DYNAMIC }; // options e.g. desired sync mode
// Create cloud synchroniser
var synchroniser = cloudSyncKit.getCloudSynchroniser(syncUrl, sessionId, deviceId, options);
Sync Mode | Value | Description |
---|---|---|
EARLIEST_FIRST |
1 | First-to-join master mode. The sync timeline is locked to the timeline that was registered first for this session. |
LOWEST_DISPERSION |
2 | Closest-client master mode. The sync timeline state is computed from the state of the timeline with the lowest reported dispersion (RTT) value. |
DYNAMIC |
4 | No master mode. The sync timeline state is computed from the timeline to have undergone the most recent significant state-change. i.e. any client can change the state of the sync timeline as long as it exports a timeline of the same type and origin (same timelineType and contentId ) |
DeviceRegistrationSuccess
and DeviceRegistrationError
events are emitted by the CloudSynchroniser object to indicate registration success or failure respectively.
A SyncServiceUnavailable
event is emitted if the connection to the Sync Service is unsuccessful.
After successful registration, the CloudSynchroniser
initialisation phase completion is indicated by WallClockAvailable
event. A synchronised wallclock is now available as the wallclock
property of the CloudSynchroniser
object.
synchroniser.on("WallClockAvailable", function () {
console.info("wallclock time:", synchroniser.wallclock.now());
});
It is recommended to use a dvbcss-clocks clock object to drive a media player and use that clock object to register the timeline. This provides more accurate reporting of the timeline progress; timestamps read from the clock object are more accurate than from the media player itself.
The library doesn't currently support registering a media player as a timeline source (this will be added to the API if there is a demand for it).
Create a clock object using the CloudSynchroniser's wallclock
as parent clock.
var videoClock = new CorrelatedClock(synchroniser.wallclock, {
tickRate: 1000, // timeline units in milliseconds
correlation: new Correlation({
parentTime: synchroniser.wallclock.now(),
childTime: videoElement.currentTime * 1000
})
});
Once registered with the Sync Service, your clock object will be updated dynamically by the service. The media player needs to be kept synchronised with the clock object. You can use the Adaptive Media Playback algorithm to locally sync the media player to the clock. This is more suitable for video. For audio, a seek might be the best option.
- An example of a clock object locked to a video element is found in
examples/synchronisedvideo/src/js/VideoClock.js
- An implementation of the Adaptive Media Playback algorithm is found in the Synchronised Video example app:
examples/synchronisedvideo/src/js/VideoSynchroniser.js
Use CloudSynchroniser's synchronise()
method to register a timeline to be used for session-wide synchronisation.
This ensures that the clock object is locked to the session-wide synchronisation timeline (generated by the service when a timeline is registered) and that the sync timeline is advertised to all clients.
var timelineType = "tag:rd.bbc.co.uk,2015-12-08:dvb:css:timeline:simple-elapsed-time:1000";
synchroniser.synchronise(videoClock, timelineType, video.currentSource().src);
-
timelineType
parameterThis is a URN string for this type of timeline and it is recommended to follow the naming scheme for DVB-CSS Timeline Selector (Section 5.3.3 of ETSI TS 103 286-2). You can make your own (and use any string you like)!
For MPG-DASH:
"urn:dvb:css:timeline:mpd:period:rel:<ticks-per-second>"
"urn:dvb:css:timeline:mpd:period:rel:<ticks-per-second>:<period-id>"
For media based on the ISOBMFF format:
"urn:dvb:css:timeline:ct"
-
contentId
parameterThis is a string that specifies the source of the timeline. Use the media source (e.g. the media URL) if available. If the timeline's source is not a media element, use a string that will be unique in this session e.g.
"my_experience_timeline_clock"
You can also use the CloudSynchroniser's addTimelineClock()
method to register a timeline for session-wide sync. Set useForSessionSync
as true
in the options
parameter.
var options = {
useForSessionSync: true
};
synchroniser.addTimelineClock(videoClock, timelineType, video.currentSource().src, options)
.then((timelineId)=>{
console.log("timeline registered, id: ", timelineId);
});
TODO:
- check returned Promise from
addTimelineClock()
You can discover timelines available for sync by
EITHER
-
Listening for the
SyncTimelinesAvailable
eventThis event contains an array of
TimelineInfo
objects representing the synchronisation timelines currently available.
OR
-
Calling the
getAvailableTimelines()
API methodgetAvailableTimelines()
asynchronously returns (via a Promise) an array of available timelines (TimelineInfo
objects).synchroniser.getAvailableTimelines() .then((timelines)=>{ console.log(timelines); });
A TimelineInfo
instance contains properties that describe/identify a timeline. Use timelineType
and contentId
to look for a timeline to sync with. The selected timeline is identified by the timelineId
property.
Use subscribeTimeline()
to subscribe to a timeline using its timelineId
. When the timeline is available locally, a TimelineAvailable
event is fired by the CloudSynchroniser object (containing the ).
var timelineClock, timelineId = "selected_timeline_id";
// subscribe to timeline and wait for TimelineAvailable event
synchroniser.subscribeTimeline(timelineId)
.then((responseCode)=>{
if responseCode === 0 console.log("success");
});
// Handle TimelineAvailable event and retrieve clock object
synchroniser.on("TimelineAvailable", function (id) {
if (id===timelineId)
timelineClock = synchroniser.getTimelineClockById(timelineId);
timelineClock.on("change", updateVideoPlayer());
});
JSDoc documentation can be built:
$ grunt jsdoc
Documentation is generated and output as HTML into the doc
subfolder.
- Rajiv Ramdhany (BBC)
- Christoph Ziegler (IRT)