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.
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.
| Field | Type | Description |
|---|---|---|
contentId | String | Unique identifier of the content item. |
offset | Long | Last watched position in milliseconds. |
timestamp | Long | Server timestamp when the bookmark was last updated. |
seasonId | String? | Season identifier (episodic content). |
episodeId | String? | Episode identifier (episodic content). |
videoId | String? | Video identifier (playlist/music-video use cases). |
mode | String? | Bookmark mode (for example, series/episode/playlist flows). |
seasonNumber | Int? | Season number (episodic content). |
episodeNumber | Int? | Episode number within season (episodic content). |
contentType | String? | Content type metadata, when available. |
playlistBookmarks | List<PlaylistBookmarkRecord>? | Per-video bookmark entries for playlist content. |
PlaylistBookmarkRecord represents bookmark data for an individual video item within a playlist.
| Field | Type | Description |
|---|---|---|
videoId | String | Unique identifier of the video in the playlist. |
offset | Long | Last watched position for this video in milliseconds. |
playlistId | String | Identifier of the parent playlist. |
timestamp | Long | Server 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 Name | Type | Default Value | Description |
|---|---|---|---|
| assetID | String | NA | The unique identifier of the content. |
| bookmarksEndPointURL | String | NA | The endpoint of Bookmarks microservice. |
| bookmarkSyncIntervalMs | Long | 10000L | The preferred time interval, in milliseconds, to periodically sync the bookmark position. The bookmark position is updated every 10 seconds by default. |
| bookmarkDeleteThreshold | Float | 0.95F | The 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. |
| bookmarkDeleteThresholdPositionMs | Long | null | Playback position in milliseconds, beyond which content is considered fully watched. This takes precedence if both bookmarkDeleteThreshold and bookmarkDeleteThresholdPositionMs provided. |
| seasonId | String? | null | (Applicable only for episodic content) The unique identifier of the season of a content. |
| episodeId | String? | null | (Applicable only for episodic content) The unique identifier of the episode of a content. |
| seasonNumber | Int? | null | (Applicable only for episodic content) The content's season number. |
| episodeNumber | Int? | null | (Applicable only for episodic content) The content's episode number in a season. |
| videoId | String? | null | TThe unique identifier of the content's music video. |
| mode | Mode? | null | (Applicable only for episodic content) The unique identifier of the Mode of a content. |
| nextEpisodeDetails | BookmarksPutRecord? | 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
)
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")
}
}