Skip to main content

Cloud Bookmarks

Bookmarks is a personalization library that provides a bookmarking service that saves the last watched position to enable the Continue Watching feature. The library facilitates maintaining the last watched position of any VOD content and provides APIs to retrieve the bookmarked position to start playback from the last watched position. You can enforce recording the last watched position either manually through your application or automatically through the library.

info

Bookmarks is not applicable for Live content. It is also not applicable for Promo / Ad with VOD content.

BookmarksService

BookmarksService declares all the APIs required to record, remove, or retrieve the last watched position of VOD content. Since all cloud bookmark network calls must be authorized, you must provide the corresponding PlatformAuthorizer object when building BookmarksService.

// endpoint of the Bookmarks microservice. You'll receive this when you onboard onto the Quickplay platform. 
// the backslash at the end should be included.
val bookmarkEndPointURL = "https://example.com/"

val bookmarksService: BookmarksService = BookmarksFactory.createBookmarksService(
bookmarkEndPointURL,
platformAuthorizer // pass the corresponding object
)

Retrieve last watched position

Your application might need the last watched position before initiating playback (for example, to display on the content details page). You should retrieve the bookmark position using BookmarksService. All these calls rely on the PlatformAuthorizer you provided to BookmarksService for authorization and user identification.

Response model

All retrieve APIs return one or more BookmarksRecord objects.

BookmarksRecord contains playback progress and optional metadata depending on content type.

FieldTypeDescription
contentIdStringUnique identifier of the content item.
offsetLongLast watched position in milliseconds.
timestampLongServer timestamp when the bookmark was last updated.
seasonIdString?Season identifier (episodic content).
episodeIdString?Episode identifier (episodic content).
videoIdString?Video identifier (playlist/music-video use cases).
modeString?Bookmark mode (for example, series/episode/playlist flows).
seasonNumberInt?Season number (episodic content).
episodeNumberInt?Episode number within season (episodic content).
contentTypeString?Content type metadata, when available.
playlistBookmarksList<PlaylistBookmarkRecord>?Per-video bookmark entries for playlist content.

PlaylistBookmarkRecord represents bookmark data for an individual video item within a playlist.

FieldTypeDescription
videoIdStringUnique identifier of the video in the playlist.
offsetLongLast watched position for this video in milliseconds.
playlistIdStringIdentifier of the parent playlist.
timestampLongServer timestamp when this video bookmark was last updated.

For a single piece of content

Use BookmarksService.getBookmark to retrieve the last watched position of a single piece of content. The API returns a BookmarksRecord object that encapsulates all the relevant information if the call succeeds, and an Error otherwise.

val contentID = "1234-5678-90ABC" // unique identifier of the content
val result = bookmarksService.getBookmark(contentID)
when (result) {
is Result.Success -> {
val bookmarksRecord = result.value

val contentId = bookmarksRecord.contentId // unique ID of the content
val bookmarkPosition = bookmarksRecord.offset // the last watched position in milliseconds, -1L for no records
val contentType = bookmarksRecord.contentType // content type, if available
val seasonId = bookmarksRecord.seasonId // the season ID of the content, if applicable
val episodeId = bookmarksRecord.episodeId // the episode ID of the content, if applicable
val seasonNumber = bookmarksRecord.seasonNumber // season number, if applicable
val episodeNumber = bookmarksRecord.episodeNumber // episode number, if applicable
val videoId = bookmarksRecord.videoId // video ID (for playlist/music-video use cases), if applicable
val mode = bookmarksRecord.mode // mode value, if available
val timestamp = bookmarksRecord.timestamp // the timestamp of when the position was recorded, -1L if not applicable
val playlistBookmarks = bookmarksRecord.playlistBookmarks // nested playlist bookmarks, if applicable
}

is Result.Failure -> {
val error = result.value
logger.error { "getBookmark failed with error $error" }
TODO("Handle getBookmark call failure as required")
}
}

For episodic content (series)

Use BookmarksService.getSeriesBookmarks to retrieve the last watched position of episodic content. The response returns a List of BookmarksRecord objects that each accurately identify the bookmark positions for each episode and season.

val seriesId = "098-765-4321" // unique identifier of the series content
val pageNumber = 1 // the page number of the bookmarks to be retrieved.
val pageSize = 10 // the page size of the bookmarks to be retrieved.
val sortByType = SortByType.TIMESTAMP // indicates to sort all the bookmarks by timestamp.
val sortOrderType = SortByType.DESC // indicates to sort all the bookmarks in descending order i.e. latest to oldest. can be reversed by passing SortByType.ASC.

val result = bookmarksService.getSeriesBookmarks(
seriesId,
pageNumber, //optional. Default value is 1.
pageSize, //optional. Default value is 10.
sortByType, //optional. Default value is SortByType.TIMESTAMP.
sortOrderType //optional. Default value is SortOrderType.DESC.
)

when (result) {
is Result.Success -> {
val bookmarksRecords = result.value
for (bookmarksRecord in bookmarksRecords) {
val contentId = bookmarksRecord.contentId // unique ID of the content
val bookmarkPosition = bookmarksRecord.offset // the last watched position in milliseconds, -1L for no records
val contentType = bookmarksRecord.contentType // content type, if available
val seasonId = bookmarksRecord.seasonId // the season ID of the content, if applicable
val episodeId = bookmarksRecord.episodeId // the episode ID of the content, if applicable
val seasonNumber = bookmarksRecord.seasonNumber // season number, if applicable
val episodeNumber = bookmarksRecord.episodeNumber // episode number, if applicable
val videoId = bookmarksRecord.videoId // video ID, if applicable
val mode = bookmarksRecord.mode // mode value, if available
val timestamp = bookmarksRecord.timestamp // the timestamp of when the position was recorded, -1L if not applicable
val playlistBookmarks = bookmarksRecord.playlistBookmarks // nested playlist bookmarks, if applicable
}
}

is Result.Failure -> {
logger.error { "getSeriesBookmarks failed with error ${result.value}" }
TODO("Handle getSeriesBookmarks call failure as required")
}
}

For an playlist content (playlist)

Use BookmarksService.getPlaylistBookmarks for retrieving the last watched position of an episodic piece of content. The response returns a List of BookmarksRecord objects that each accurately identify the bookmark positions for each episode and season.

val playlistId = "098-765-4321" // unique identifier of the playlist content
val pageNumber = 1 // the page number of the bookmarks to be retrieved.
val pageSize = 10 // the page size of the bookmarks to be retrieved.
val sortByType = SortByType.TIMESTAMP // indicates to sort all the bookmarks by timestamp.
val sortOrderType = SortByType.DESC // indicates to sort all the bookmarks in descending order i.e. latest to oldest. can be reversed by passing SortByType.ASC.

val result = bookmarksService.getPlaylistBookmarks(
playlistId,
pageNumber, //optional. Default value is 1.
pageSize, //optional. Default value is 10.
sortByType, //optional. Default value is SortByType.TIMESTAMP.
sortOrderType //optional. Default value is SortOrderType.DESC.
)

when (result) {
is Result.Success -> {
val bookmarksRecords = result.value
for (bookmarksRecord in bookmarksRecords) {
val contentId = bookmarksRecord.contentId // unique ID of the playlist
val bookmarkPosition = bookmarksRecord.offset // playlist-level offset, if available
val mode = bookmarksRecord.mode // expected to be playlist mode
val timestamp = bookmarksRecord.timestamp // record timestamp

bookmarksRecord.playlistBookmarks?.forEach { playlistBookmark ->
val videoId = playlistBookmark.videoId // unique ID of the video item in the playlist
val videoBookmarkPosition = playlistBookmark.offset // last watched position for this video item
val playlistIdFromItem = playlistBookmark.playlistId // playlist ID for this video item
val videoTimestamp = playlistBookmark.timestamp // timestamp for this video item
}
}
}

is Result.Failure -> {
logger.error { "getSeriesBookmarks failed with error ${result.value}" }
TODO("Handle getSeriesBookmarks call failure as required")
}
}

For a specific user

Use the BookmarksService.getBookmarks API to retrieve all the incompletely watched content positions for a specific user. The response returns a list of BookmarksRecord objects that each accurately identify the content and position.

val pageNumber = 1 // the page number of the bookmarks to be retrieved.
val pageSize = 10 // the page size of the bookmarks to be retrieved.
val sortByType = SortByType.TIMESTAMP // indicates to sort all the bookmarks by timestamp.
val sortOrderType = SortByType.DESC // indicates to sort all the bookmarks in descending order i.e. latest to oldest. can be reversed by passing SortByType.ASC.

val result = bookmarksService.getBookmarks(
pageNumber, //optional. Default value is 1.
pageSize, //optional. Default value is 10.
sortByType, //optional. Default value is SortByType.TIMESTAMP.
sortOrderType //optional. Default value is SortOrderType.DESC.
)

when (result) {
is Result.Success -> {
val bookmarksRecords = result.value
for (bookmarksRecord in bookmarksRecords) {
val contentId = bookmarksRecord.contentId // unique ID of the content
val bookmarkPosition = bookmarksRecord.offset // the last watched position in milliseconds, -1L for no records
val contentType = bookmarksRecord.contentType // content type, if available
val seasonId = bookmarksRecord.seasonId // the season ID of the content, if applicable
val episodeId = bookmarksRecord.episodeId // the episode ID of the content, if applicable
val seasonNumber = bookmarksRecord.seasonNumber // season number, if applicable
val episodeNumber = bookmarksRecord.episodeNumber // episode number, if applicable
val videoId = bookmarksRecord.videoId // video ID, if applicable
val mode = bookmarksRecord.mode // mode value, if available
val timestamp = bookmarksRecord.timestamp // the timestamp of when the position was recorded, -1L if not applicable
val playlistBookmarks = bookmarksRecord.playlistBookmarks // nested playlist bookmarks, if applicable
}
}

is Result.Failure -> {
logger.error { "getBookmarks failed with error ${result.value}" }
TODO("Handle getBookmarks call failure as required")
}
}

Automatic enforcement

The library can automatically maintain the last played position of content using BookmarksManager. You should create a BookmarksManager that relies on the ComposablePlayer you provide to automatically update the last played position at appropriate intervals.

Create BookmarksConfiguration

BookmarksConfiguration encapsulates all the necessary information required to automate bookmarks.

Property NameTypeDefault ValueDescription
assetIDStringNAThe unique identifier of the content.
bookmarksEndPointURLStringNAThe endpoint of Bookmarks microservice.
bookmarkSyncIntervalMsLong10000LThe preferred time interval, in milliseconds, to periodically sync the bookmark position. The bookmark position is updated every 10 seconds by default.
bookmarkDeleteThresholdFloat0.95FThe threshold factor used to calculate the current asset's playback position which determines if the asset's bookmark should be purged from the Bookmark Microservice or not. Valid value should be in the range (0, 1]. By default, Bookmark is removed only if the content is watched till the very end of the content's duration.
bookmarkDeleteThresholdPositionMsLongnullPlayback position in milliseconds, beyond which content is considered fully watched. This takes precedence if both bookmarkDeleteThreshold and bookmarkDeleteThresholdPositionMs provided.
seasonIdString?null(Applicable only for episodic content) The unique identifier of the season of a content.
episodeIdString?null(Applicable only for episodic content) The unique identifier of the episode of a content.
seasonNumberInt?null(Applicable only for episodic content) The content's season number.
episodeNumberInt?null(Applicable only for episodic content) The content's episode number in a season.
videoIdString?nullTThe unique identifier of the content's music video.
modeMode?null(Applicable only for episodic content) The unique identifier of the Mode of a content.
nextEpisodeDetailsBookmarksPutRecord?null(Applicable only for episodic content) The [BookmarksPutRecord] instance that holds all the relevant metadata of the next episode.
val bookmarkEndPoint = "https://bookmarksEndpoint.url" //Bookmarks Microservice URL
val contentID = "123ABC-LC131-PLOJ45" // Unique identifier of the content
val bookmarksSyncIntervalMs = 15_000L // pass preferred value
val bookmarksDeleteThreshold = 0.98F // pass preferred value
val bookmarkDeleteThresholdPositionMs = 1800000 // pass preferred value
val seasonId = "mySeasonId" // pass preferred value
val episodeId = "myEpisodeId" // pass preferred value
val seasonNumber = 1 // pass preferred value
val episodeNumber = 4 // pass preferred value
val mode = Mode.SERIES // pass preferred value - either Mode.SERIES or Mode.EPISODE

val bookmarksConfiguration = BookmarksManager.BookmarksConfiguration(
assetID = contentID,
bookmarksEndPointURL = bookmarkEndPoint,
bookmarkSyncIntervalMs = bookmarksSyncIntervalMs, // Optional, default value is 10000L
bookmarkDeleteThreshold = bookmarksDeleteThreshold, // Optional, default value is 0.95F
bookmarkDeleteThresholdPositionMs = bookmarkDeleteThresholdPositionMs, // optional, default value is null
seasonId = seasonId, // Optional, default value is null
episodeId = episodeId, // Optional, default value is null
seasonNumber = seasonNumber, // Optional, default value is null
episodeNumber = episodeNumber, // Optional, default value is null
mode = mode // Optional, default value is null
)

Playlist Bookmark Configuration

val bookmarkEndPoint = "https://bookmarksEndpoint.url" //Bookmarks Microservice URL
val playlistId = "123ABC-LC131-PLOJ45" // Unique identifier of the playlist
val bookmarksSyncIntervalMs = 15_000L // pass preferred value
val bookmarksDeleteThreshold = 0.98F // pass preferred value
val bookmarkDeleteThresholdPositionMs = 1800000 // pass preferred value
val videoId = "myVideoId" // Unique identifier of the specific video inside playlist
val mode = Mode.PLAYLIST // pass preferred value

val bookmarksConfiguration = BookmarksManager.BookmarksConfiguration(
assetID = playlistId,
bookmarksEndPointURL = bookmarkEndPoint,
bookmarkSyncIntervalMs = bookmarksSyncIntervalMs, // Optional, default value is 10000L
bookmarkDeleteThreshold = bookmarksDeleteThreshold, // Optional, default value is 0.95F
bookmarkDeleteThresholdPositionMs = bookmarkDeleteThresholdPositionMs, // optional, default value is null
videoId = videoId,
mode = mode
)

Create BookmarksManager

Once you create a BookmarksManager, it takes care of automating all the API calls required to maintain and delete the bookmark position of content. You must create a new BookmarkManager before the start of any new VOD playback to maintain accuracy.

BookmarksFactory.createBookmarksManager(
composablePlayer, // the relevant ComposablePlayer instance
platformAuthorizer, // the relevant PlatformAuthorizer instance
bookmarksConfiguration, // the relevant BookmarksConfiguration instance
httpClient // (Optional) the HTTP client to be used
)
note

You can convert a normal Player into a ComposablePlayer using the composablePlayerWith function.

Once you create BookmarksManager, it ensures that the content position is updated periodically and also on applicable player state changes. It also takes care of deleting the bookmark when you can consider the content to be watched entirely (Use BookmarksConfiguration.bookmarkDeleteThreshold to tweak this behavior).

Manual enforcement

You can manually maintain the last played position of content using BookmarksService.

val contentID = "123ABC-LC131-PLOJ45" // Unique ID of the content

// Record the bookmark position of a content
val latestPositionMs = 99999L
val putBookmarkResult = bookmarksService.putBookmark(contentID, latestPositionMs)
when (putBookmarkResult) {
is Result.Success -> {
logger.info { "Latest bookmark at position $latestPositionMs recorded for content ID $contentID"}
}

is Result.Failure -> {
logger.error { "putBookmark failed with error ${result.value}" }
TODO("Handle putBookmark call failure, if required")
}
}

// Remove the recorded bookmark position of a content
val deleteBookmarkResult = bookmarksService.deleteBookmark(contentID)
when (deleteBookmarkResult) {
is Result.Success -> {
logger.info { "Bookmark deleted for content ID $contentID"}
}

is Result.Failure -> {
logger.error { "deleteBookmark failed with error ${result.value}" }
TODO("Handle deleteBookmark call failure, if required")
}
}