Skip to content

Commit

Permalink
Merge pull request #3 from apivideo/feature/custom_domains
Browse files Browse the repository at this point in the history
feat(player): add support for custom domain for `VideoOptions.fromURL`
  • Loading branch information
ThibaultBee committed Jun 5, 2023
2 parents 66df66c + 8c73b87 commit 1f2f70a
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fun String.toVideoType() = VideoType.fromString(this)
*
* @return corresponding [VideoOptions] or an exception
*/
fun String.parseAsVideoOptions(): VideoOptions {
return Utils.parseMediaUrl(this)
}
fun String.parseAsVideoOptions(
vodDomainURL: String = VideoType.VOD.baseUrl,
liveDomainURL: String = VideoType.LIVE.baseUrl
) = Utils.parseMediaUrl(this, vodDomainURL, liveDomainURL)
16 changes: 16 additions & 0 deletions player/src/main/java/video/api/player/extensions/URLExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package video.api.player.extensions

import video.api.player.models.VideoOptions
import video.api.player.models.VideoType
import video.api.player.utils.Utils
import java.net.URL

/**
* Converts an api.video URL String to a [VideoOptions].
*
* @return corresponding [VideoOptions] or an exception
*/
fun URL.parseAsVideoOptions(
vodDomainURL: URL = URL(VideoType.VOD.baseUrl),
liveDomainURL: URL = URL(VideoType.LIVE.baseUrl)
) = Utils.parseMediaUrl(this, vodDomainURL, liveDomainURL)
19 changes: 18 additions & 1 deletion player/src/main/java/video/api/player/models/VideoOptions.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package video.api.player.models

import video.api.player.extensions.parseAsVideoOptions
import video.api.player.utils.Utils
import java.net.URL

/**
* Description of the video to play.
Expand Down Expand Up @@ -40,6 +42,21 @@ data class VideoOptions(
*
* @param url the URL of the video to play
*/
fun fromUrl(url: String) = url.parseAsVideoOptions()
fun fromUrl(
url: String,
vodDomainURL: String = VideoType.VOD.baseUrl,
liveDomainURL: String = VideoType.LIVE.baseUrl
) = url.parseAsVideoOptions(vodDomainURL, liveDomainURL)

/**
* Creates a [VideoOptions] from an api.video URL.
*
* @param url the URL of the video to play
*/
fun fromUrl(
url: URL,
vodDomainURL: URL = URL(VideoType.VOD.baseUrl),
liveDomainURL: URL = URL(VideoType.LIVE.baseUrl)
) = url.parseAsVideoOptions(vodDomainURL, liveDomainURL)
}
}
38 changes: 26 additions & 12 deletions player/src/main/java/video/api/player/utils/Utils.kt
Original file line number Diff line number Diff line change
@@ -1,37 +1,53 @@
package video.api.player.utils

import video.api.player.extensions.toVideoType
import video.api.player.models.VideoOptions
import video.api.player.models.VideoType
import java.io.IOException
import java.net.URL
import java.util.regex.Pattern

object Utils {
private const val VOD_TOKEN_DELIMITER = "token"
private const val LIVE_TOKEN_DELIMITER = "private"

fun parseMediaUrl(mediaUrl: String): VideoOptions {
fun parseMediaUrl(
mediaUrl: String,
vodDomainURL: String = VideoType.VOD.baseUrl,
liveDomainURL: String = VideoType.LIVE.baseUrl
) = parseMediaUrl(URL(mediaUrl), URL(vodDomainURL), URL(liveDomainURL))

fun parseMediaUrl(
mediaUrl: URL,
vodDomainURL: URL = URL(VideoType.VOD.baseUrl),
liveDomainURL: URL = URL(VideoType.LIVE.baseUrl)
): VideoOptions {
val regex =
"https:/.*[/](?<type>vod|live).*/(?<id>(vi|li)[^/^.]*)[/.].*"
"https:/.*[/].*/(?<id>(vi|li)[^/^.]*)[/.].*"
val pattern = Pattern.compile(regex)
val matcher = pattern.matcher(mediaUrl)
val matcher = pattern.matcher(mediaUrl.toString())

if (matcher.groupCount() < 3) {
if (matcher.groupCount() < 2) {
throw IOException("The media url doesn't look like an api.video URL.")
}

try {
matcher.find()
// Group naming is not supported before Android API 26
val videoType =
matcher.group(1)?.toVideoType() ?: throw IOException("Failed to get video type")
val videoId = matcher.group(2) ?: throw IOException("Failed to get videoId")
if (mediaUrl.toString().startsWith(vodDomainURL.toString())) {
VideoType.VOD
} else if (mediaUrl.toString().startsWith(liveDomainURL.toString())) {
VideoType.LIVE
} else {
throw IOException("The media url must start with $vodDomainURL or $liveDomainURL")
}
val videoId = matcher.group(1) ?: throw IOException("Failed to get videoId")

val tokenDelimiter =
if (videoType == VideoType.VOD) VOD_TOKEN_DELIMITER else LIVE_TOKEN_DELIMITER
val token =
if (mediaUrl.contains("$tokenDelimiter/")) {
mediaUrl.substringAfter("$tokenDelimiter/").substringBefore('/')
if (mediaUrl.toString().contains("$tokenDelimiter/")) {
mediaUrl.toString().substringAfter("$tokenDelimiter/").substringBefore('/')
} else {
null
}
Expand All @@ -42,9 +58,7 @@ object Utils {
token
)
} catch (e: Exception) {
e.message?.let {
throw IOException("The media url doesn't look like an api.video URL: $it")
} ?: throw IOException("The media url doesn't look like an api.video URL.")
throw IOException("The media url doesn't look like an api.video URL", e)
}
}
}
73 changes: 68 additions & 5 deletions player/src/test/java/video/api/player/models/VideoOptionsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ class VideoOptionsTest {
@Test
fun `test mp4 url`() {
val videoOptions = VideoOptions("vi5oNqxkifcXkT4auGNsvgZB", VideoType.VOD)
assertEquals("https://vod.api.video/vod/vi5oNqxkifcXkT4auGNsvgZB/mp4/source.mp4", videoOptions.mp4Url)
assertEquals(
"https://vod.api.video/vod/vi5oNqxkifcXkT4auGNsvgZB/mp4/source.mp4",
videoOptions.mp4Url
)
}

@Test
Expand Down Expand Up @@ -64,6 +67,15 @@ class VideoOptionsTest {
assertEquals(videoOptions.token, null)
}

@Test
fun `test parse private vod url`() {
val videoOptions =
VideoOptions.fromUrl("https://vod.api.video/vod/vi5oNqxkifcXkT4auGNsvgZB/token/PRIVATE_TOKEN/hls/manifest.m3u8")
assertEquals(videoOptions.videoId, "vi5oNqxkifcXkT4auGNsvgZB")
assertEquals(videoOptions.videoType, VideoType.VOD)
assertEquals(videoOptions.token, "PRIVATE_TOKEN")
}

@Test
fun `test parse MP4 vod url`() {
val videoOptions =
Expand All @@ -74,18 +86,18 @@ class VideoOptionsTest {
}

@Test
fun `test parse private vod url`() {
fun `test parse private MP4 vod url`() {
val videoOptions =
VideoOptions.fromUrl("https://vod.api.video/vod/vi5oNqxkifcXkT4auGNsvgZB/token/PRIVATE_TOKEN/hls/manifest.m3u8")
VideoOptions.fromUrl("https://vod.api.video/vod/vi5oNqxkifcXkT4auGNsvgZB/token/PRIVATE_TOKEN/mp4/source.mp4")
assertEquals(videoOptions.videoId, "vi5oNqxkifcXkT4auGNsvgZB")
assertEquals(videoOptions.videoType, VideoType.VOD)
assertEquals(videoOptions.token, "PRIVATE_TOKEN")
}


@Test
fun `test parse live url`() {
val videoOptions = VideoOptions.fromUrl("https://live.api.video/li77ACbZjzEJgmr8d0tm4xFt.m3u8")
val videoOptions =
VideoOptions.fromUrl("https://live.api.video/li77ACbZjzEJgmr8d0tm4xFt.m3u8")
assertEquals(videoOptions.videoId, "li77ACbZjzEJgmr8d0tm4xFt")
assertEquals(videoOptions.videoType, VideoType.LIVE)
assertEquals(videoOptions.token, null)
Expand All @@ -99,4 +111,55 @@ class VideoOptionsTest {
assertEquals(videoOptions.videoType, VideoType.LIVE)
assertEquals(videoOptions.token, "PRIVATE_TOKEN")
}

@Test
fun `test parse vod url with custom domain`() {
val videoOptions =
VideoOptions.fromUrl(
"https://mycustom.vod.domain/vod/vi5oNqxkifcXkT4auGNsvgZB/hls/manifest.m3u8",
"https://mycustom.vod.domain",
"https://mycustom.live.domain"
)
assertEquals(videoOptions.videoId, "vi5oNqxkifcXkT4auGNsvgZB")
assertEquals(videoOptions.videoType, VideoType.VOD)
assertEquals(videoOptions.token, null)
}

@Test
fun `test parse private vod url with custom domain`() {
val videoOptions =
VideoOptions.fromUrl(
"https://mycustom.vod.domain/vod/vi5oNqxkifcXkT4auGNsvgZB/token/PRIVATE_TOKEN/hls/manifest.m3u8",
"https://mycustom.vod.domain",
"https://mycustom.live.domain"
)
assertEquals(videoOptions.videoId, "vi5oNqxkifcXkT4auGNsvgZB")
assertEquals(videoOptions.videoType, VideoType.VOD)
assertEquals(videoOptions.token, "PRIVATE_TOKEN")
}

@Test
fun `test parse live url with custom domain`() {
val videoOptions = VideoOptions.fromUrl(
"https://mycustom.live.domain/li77ACbZjzEJgmr8d0tm4xFt.m3u8",
"https://mycustom.vod.domain",
"https://mycustom.live.domain"
)
assertEquals(videoOptions.videoId, "li77ACbZjzEJgmr8d0tm4xFt")
assertEquals(videoOptions.videoType, VideoType.LIVE)
assertEquals(videoOptions.token, null)
}

@Test
fun `test parse private live url with custom domain`() {
val videoOptions =
VideoOptions.fromUrl(
"https://mycustom.live.domain/private/PRIVATE_TOKEN/li77ACbZjzEJgmr8d0tm4xFt.m3u8",
"https://mycustom.vod.domain",
"https://mycustom.live.domain"
)
assertEquals(videoOptions.videoId, "li77ACbZjzEJgmr8d0tm4xFt")
assertEquals(videoOptions.videoType, VideoType.LIVE)
assertEquals(videoOptions.token, "PRIVATE_TOKEN")
}
}

0 comments on commit 1f2f70a

Please sign in to comment.