api.video

Features

Tutorials

Resume a video

January 12, 2021 - Doug

When I read a book, I fold a corner over the page I am reading to remember my place. Sometimes I fall asleep while reading, and it takes a few minutes to figure out where I had finished the night before, and I wish to myself that I had folded over the corner.

Watching video online is the same way - if you're interrupted while watching a long video (Amazon delivery at the door, child is hungry, cat needs to be let out... you know) it would be nice to resume right where you left off.

Resuming videos

YouTube recently remembered where I had left off on a knitting tutorial.. that I had watched 3 years ago. I have small children, so we often have to pause movies for days before we finish them, and it is great that Netflix recalls that we turned off the TV at 42:11 into the feature. No more scanning and seeing potential spoilers a scene or two ahead.

We can do the same with api.video's videos. We've released a demo at resume.a.video where you can test this feature out.

Resuming Videos with api.video

Identifying users

For each video that you'd like to resume, you should create a dynamic metadata attribute that you will use to identify each user. For the videos in the demo, I created a dynamic metadata with key "userName"

A screenshot of go.api.video showing the metadata userName set

You can create a Dynamic metadata with the Update video endpoint. For the 'key' use the key. For 'value' add two'_' before and after the term. The API will know that this value can have any potential value.

You can also use the GUI at go.api.video to add the metadata.

We can use this to uniquely identify users who watch the video. I would recommend using a unique non-identifiable string for each user, but for simplicity, I'll use first names in this demo. In this way, we can identify who is watching the video:

https://embed.api.video/vod/vi4fvaDG9nqfdMTqRnmZuvlJ?metadata[userName]=Doug

This video will record its session with the metadata userName: Doug.

Awesome - assuming we know the user's name, we can insert this URL paramater and now identify all of their sessions with the video.

Identifying user sessions

When you use the api.video player, each session has analytics recorded (and each session is based on a cookie set in the browser). Using the analytics video session endpoint, you can search each session for a video played. You can limit this search with the metadata term. In NodeJS, this looks like:

var firstSessionQuery = {
			method: 'GET',
			url: 'https://ws.api.video/analytics/videos/'+videoId+'?currentPage=1&pageSize=1&metadata[userName]='+userName,
			headers: {
				accept: 'application/json',
				authorization: 'Bearer ' +authToken
			}
		}
		request(firstSessionQuery, function (error, response, body) {
			if (error) throw new Error(error);
			body = JSON.parse(body);
		}

The request url uses the videoId and userName to get just the sessions for the user. The response can return from 1 to 100 results, with the most recent session appearing at the end. In this query, I only request the first session:

data {
  session: {
    sessionId: 'ps5qU6t436lzoDftbAfuJPzP',
    loadedAt: '2021-01-05T12:52:21+00:00',
    endedAt: '2021-01-05T12:52:53+00:00',
    metadata: [ [Object] ]
  },
  location: { country: 'United Kingdom', city: 'Watford' },
  referrer: {
    url: null,
    medium: 'unknown',
    source: 'unknown',
    searchTerm: 'unknown'
  },
  device: { type: 'computer', vendor: 'unknown', model: 'Other' },
  os: { name: 'Mac OS X', shortname: 'Mac OS X', version: '11.0.1' },
  client: { type: 'unknown', name: 'Chrome', version: '87.0.4280' }
}
pagination {
  currentPage: 1,
  currentPageItems: 1,
  pageSize: 1,
  pagesTotal: 23,
  itemsTotal: 23,
  links: [
    {
      rel: 'self',
      uri: '/analytics/videos/vi4fvaDG9nqfdMTqRnmZuvlJ?currentPage=1&pageSize=1&metadata%5BuserName%5D=Doug'
    },
    {
      rel: 'first',
      uri: '/analytics/videos/vi4fvaDG9nqfdMTqRnmZuvlJ?currentPage=1&pageSize=1&metadata%5BuserName%5D=Doug'
    },
    {
      rel: 'next',
      uri: '/analytics/videos/vi4fvaDG9nqfdMTqRnmZuvlJ?currentPage=2&pageSize=1&metadata%5BuserName%5D=Doug'
    },
    {
      rel: 'last',
      uri: '/analytics/videos/vi4fvaDG9nqfdMTqRnmZuvlJ?currentPage=23&pageSize=1&metadata%5BuserName%5D=Doug'
    }
  ]
}

Ok, so when I created this video in January 2021, I watched it, to ensure that it worked. You can see my approximate location (it's really where my ISP exits to the internet), and I was on a Mac running Chrome.

So, why just one entry? We want to get the last session that was viewed, and that is always the last session in the list.

var sessionCount = body.pagination.itemsTotal;

There are really only three possibilities to worry about here:

  • sessionCount = 0. This is a new viewer of the video, and there are no sessions saved. If there are no sessions for the user - we can immediately serve the page, and start the video at the beginning:
				//no sessions for this user
				//return the video at time =0
				var videoUrl = 'https://embed.api.video/vod/'+videoId+'?metadata[userName]='+userName+'#autoplay';
				return res.render('video', {videoUrl});

This will load the page with the video autoplaying.

  • sessionCount=1. The viewer has watched the video once. If that is the case, the sessionId returned in the response will be the session we want. You can now skip ahead to the "parsing session data" section.
  • sessionCount >1. There is more than one session for this user. In the example above, the itemsTotal parameter indicates that there are 23 sessions for userName=Doug.

The API has a limit of 100 sessions in the analytics response. In general, one user is unlikely to have >100 sessions for a single video, so one query would probably be sufficient. However, this is a demo app with a default user - it is possible that we might exceed 100 sessions. If I queried for 100 sessions, the demo would appear to fail - we'd only ever see the 100th session. So, we request one, and then to ensure that we always get the last session, we can now make a second query for the last session (conveniently in the pagination.links.last.uri).

var lastUri = decodeURI(body.pagination.links[linksLength-1].uri);
					console.log("number of sessions with this username:", lastSessionCounter);
					console.log("uri", lastUri);
					//request that last session
					var lastSessionQuery = {
						method: 'GET',
						url: 'https://ws.api.video' +lastUri,
						headers: {
							accept: 'application/json',
							authorization: 'Bearer ' +authToken
						}
					}
					request(lastSessionQuery, function (error, response, body) {
						if (error) throw new Error(error);
						body = JSON.parse(body);
						console.log("body", body);
						console.log(body.data[0]);
						var sessionId = body.data[0].session.sessionId;
						console.log(sessionId);
						//now we can access this sessionId, and get the last time in the session
						getSession(authToken, videoId, sessionId, userName);

This response now tells me the last session for Doug and this video:

 session: {
    sessionId: 'pshSgwO44hBbc0nYKz1OZsz',
    loadedAt: '2021-01-11T19:02:43+00:00',
    endedAt: '2021-01-12T11:35:49+00:00',
    metadata: [ [Object] ]
  },
  location: { country: 'United Kingdom', city: 'Slough' },
  referrer: {
    url: 'http://localhost:3006/',
    medium: 'unknown',
    source: 'unknown',
    searchTerm: 'unknown'
  },
  device: { type: 'computer', vendor: 'unknown', model: 'Other' },
  os: { name: 'Mac OS X', shortname: 'Mac OS X', version: '11.0.1' },
  client: { type: 'unknown', name: 'Chrome', version: '87.0.4280' }
}

Again - on my Mac - but this time, I am in Slough, UK (and I was testing this app locally on my computer). The sessionId I want is 'pshSgwO44hBbc0nYKz1OZsz'

Parsing session data

If there was a previous session for this user - we now have the last session (and more importantly the sessionId for that session). It may have just been one request - or it may have taken two.

I use the session events endpoint to find out what happened in the session:

	function getSession (authToken, videoId, sessionId, userName) {
		//lets get 100 session activities - i doubt there will ever be that many - (or more than 100)
		var getSession = {
			method: 'GET',
			url: 'https://ws.api.video/analytics/sessions/'+sessionId+'/events?currentPage=1&pageSize=100',
			headers: {
				accept: 'application/json',
				authorization: 'Bearer ' +authToken
			}
		}
		request(getSession, function (error, response, body) {
			if (error) throw new Error(error);
			body = JSON.parse(body);
			console.log("sessions", body.data);
			var numberofEvents = body.data.length;
			console.log("num events", numberofEvents);
			var lastTimeRecorded = body.data[numberofEvents-1].at;
			console.log("last event time", lastTimeRecorded);
	
			var videoUrl = 'https://embed.api.video/vod/'+videoId+'?metadata[userName]='+userName+'#autoplay;t='+lastTimeRecorded;
					return res.render('video', {videoUrl});
	
		});
	
	}
	
	  
});

So the API calls the session endpoint with the sessionId we discovered in the last section. We get a response (write to console log) and we extract how many events were in the last session, and find out what the last event was.

You'll note that in the comments I make an assumption that there will never be 100 events (play, pause, seek, etc.) in a single session. I think this is safe to make :)

The session response looks like this:

sessions [
  { type: 'ready', emittedAt: '2021-01-12T11:59:42+00:00', at: 0 },
  { type: 'play', emittedAt: '2021-01-12T11:59:42+00:00', at: 0 },
  { type: 'pause', emittedAt: '2021-01-12T11:59:45+00:00', at: 2 },
  {
    type: 'seek.forward',
    emittedAt: '2021-01-12T11:59:46+00:00',
    from: 2,
    to: 163
  },
  { type: 'resume', emittedAt: '2021-01-12T11:59:47+00:00', at: 163 },
  { type: 'pause', emittedAt: '2021-01-12T11:59:49+00:00', at: 165 }
]
num events 6
last event time 165

So I started the video at time =0, and then at time t=2, I jumped ahead "seek.forward" to 163s. Then I paused at 165s.

So the last time viewed was at 165s.

Starting the video

We have now found all the sessions for the user. We grabbed the last (most recent) session, and then found the last event in that session - to give us the last time that the user saw the video.

In HTML5, if you add #t=X, where X is an integer to the end of a video URL, the video playback will begin at that time. Simply returning the url with this parameter will start the video at the last time the user viewed the video.

Summary

With Dynamic metadata, we are able to assign video watching sessions to specific customers. We can then search a specific user's sessions for the most recent session, and inside that session, parse all the events that occured during playback.

Using the last session, and the last event, we get the last time that the viewer interacted with the video. We can now use this time as the "resume time" for the video, and stat the video at that time.

Don't forget to try it out: resume.a.video

This is a nice feature that makes watching longer videos easier - your viewers know they can come back and quickly pick up where they left off.

Are you thinking about using 'resume' in your videos? The code is all up on Github Let us know - we'd love to see if it improves the engagement of your viewers! Leave us a comment in the community forum.

Doug

Developer Evangelist

Get started now

Connect your users with videos