Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Implement video stream priority for native videos #62

Open
wants to merge 3 commits into
base: 2U/develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ val screenModule = module {
get(),
get(),
get(),
get(),
)
}
viewModel { (courseId: String, courseTitle: String) ->
Expand Down
7 changes: 5 additions & 2 deletions core/src/main/java/org/openedx/core/data/model/Block.kt
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,15 @@ data class VideoInfo(
@SerializedName("url")
var url: String?,
@SerializedName("file_size")
var fileSize: Int?
var fileSize: Long?,
@SerializedName("stream_priority")
var streamPriority: Int?,
) {
fun mapToDomain(): DomainVideoInfo {
return DomainVideoInfo(
url = url ?: "",
fileSize = fileSize ?: 0
fileSize = fileSize ?: 0,
streamPriority = streamPriority ?: 0,
)
}
}
Expand Down
11 changes: 7 additions & 4 deletions core/src/main/java/org/openedx/core/data/model/room/BlockDb.kt
Original file line number Diff line number Diff line change
Expand Up @@ -193,16 +193,19 @@ data class VideoInfoDb(
@ColumnInfo("url")
val url: String,
@ColumnInfo("fileSize")
val fileSize: Int
val fileSize: Long,
@ColumnInfo("streamPriority")
val streamPriority: Int,
) {
fun mapToDomain() = DomainVideoInfo(url, fileSize)
fun mapToDomain() = DomainVideoInfo(url, fileSize, streamPriority)

companion object {
fun createFrom(videoInfo: VideoInfo?): VideoInfoDb? {
if (videoInfo == null) return null
return VideoInfoDb(
videoInfo.url ?: "",
videoInfo.fileSize ?: 0,
url = videoInfo.url ?: "",
fileSize = videoInfo.fileSize ?: 0,
streamPriority = videoInfo.streamPriority ?: 0,
)
}
}
Expand Down
53 changes: 51 additions & 2 deletions core/src/main/java/org/openedx/core/domain/model/Block.kt
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ data class EncodedVideos(
|| hls?.url != null
|| fallback?.url != null

val videoUrl: String
private val videoUrl: String
get() = fallback?.url
?: hls?.url
?: desktopMp4?.url
Expand All @@ -133,6 +133,54 @@ data class EncodedVideos(
val hasYoutubeUrl: Boolean
get() = youtube?.url?.isNotEmpty() == true

fun getPreferredVideoInfoForStreaming(preferredVideoStreaming: VideoQuality): VideoInfo {
return when (preferredVideoStreaming) {
VideoQuality.AUTO -> {
listOfNotNull(
mobileLow,
mobileHigh,
desktopMp4,
hls,
youtube,
fallback,
).minBy { it.streamPriority }
}

VideoQuality.OPTION_720P -> {
listOfNotNull(
desktopMp4,
mobileHigh,
mobileLow,
hls,
youtube,
fallback,
).first()
}

VideoQuality.OPTION_540P -> {
listOfNotNull(
mobileHigh,
mobileLow,
desktopMp4,
hls,
youtube,
fallback,
).first()
}

VideoQuality.OPTION_360P -> {
listOfNotNull(
mobileLow,
mobileHigh,
desktopMp4,
hls,
youtube,
fallback,
).first()
}
}
}

fun getPreferredVideoInfoForDownloading(preferredVideoQuality: VideoQuality): VideoInfo? {
var preferredVideoInfo = when (preferredVideoQuality) {
VideoQuality.OPTION_360P -> mobileLow
Expand Down Expand Up @@ -186,7 +234,8 @@ data class EncodedVideos(

data class VideoInfo(
val url: String,
val fileSize: Int,
val fileSize: Long,
val streamPriority: Int,
)

data class BlockCounts(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class DownloadWorker(
DownloadModelEntity.createFrom(
downloadTask.copy(
downloadedState = DownloadedState.DOWNLOADED,
size = File(downloadTask.path).length().toInt()
size = File(downloadTask.path).length()
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.openedx.core.module.db
data class DownloadModel(
val id: String,
val title: String,
val size: Int,
val size: Long,
val path: String,
val url: String,
val type: FileType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ data class DownloadModelEntity(
@ColumnInfo("title")
val title: String,
@ColumnInfo("size")
val size: Int,
val size: Long,
@ColumnInfo("path")
val path: String,
@ColumnInfo("url")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class CourseUnitContainerAdapter(
val videoUrl = if (viewModel.getDownloadModelById(block.id) != null) {
isDownloaded = true
viewModel.getDownloadModelById(block.id)!!.path
} else videoUrl
} else getPreferredVideoInfoForStreaming(viewModel.videoQuality).url
if (videoUrl.isNotEmpty()) {
VideoUnitFragment.newInstance(
block.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import kotlinx.coroutines.runBlocking
import org.openedx.core.BaseViewModel
import org.openedx.core.BlockType
import org.openedx.core.config.Config
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.model.Block
import org.openedx.core.extension.clearAndAddAll
import org.openedx.core.extension.indexOfFirstFromIndex
Expand All @@ -33,6 +34,7 @@ class CourseUnitContainerViewModel(
private val interactor: CourseInteractor,
private val notifier: CourseNotifier,
private val analytics: CourseAnalytics,
private val corePreferences: CorePreferences,
) : BaseViewModel() {

private val blocks = ArrayList<Block>()
Expand Down Expand Up @@ -82,6 +84,9 @@ class CourseUnitContainerViewModel(
private val _descendantsBlocks = MutableStateFlow<List<Block>>(listOf())
val descendantsBlocks = _descendantsBlocks.asStateFlow()

val videoQuality
get() = corePreferences.videoSettings.videoStreamingQuality

fun loadBlocks(mode: CourseViewMode, componentId: String = "") {
currentMode = mode
viewModelScope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.junit.Test
import org.junit.rules.TestRule
import org.openedx.core.BlockType
import org.openedx.core.config.Config
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.domain.model.AssignmentProgress
import org.openedx.core.domain.model.Block
import org.openedx.core.domain.model.BlockCounts
Expand All @@ -46,6 +47,7 @@ class CourseUnitContainerViewModelTest {
private val interactor = mockk<CourseInteractor>()
private val notifier = mockk<CourseNotifier>()
private val analytics = mockk<CourseAnalytics>()
private val corePreferences = mockk<CorePreferences>()

private val assignmentProgress = AssignmentProgress(
assignmentType = "Homework",
Expand Down Expand Up @@ -181,7 +183,15 @@ class CourseUnitContainerViewModelTest {
fun `getBlocks no internet connection exception`() = runTest {
every { notifier.notifier } returns MutableSharedFlow()
val viewModel =
CourseUnitContainerViewModel("", "", config, interactor, notifier, analytics)
CourseUnitContainerViewModel(
"",
"",
config,
interactor,
notifier,
analytics,
corePreferences
)

coEvery { interactor.getCourseStructure(any()) } throws UnknownHostException()
coEvery { interactor.getCourseStructureForVideos(any()) } throws UnknownHostException()
Expand All @@ -196,7 +206,15 @@ class CourseUnitContainerViewModelTest {
fun `getBlocks unknown exception`() = runTest {
every { notifier.notifier } returns MutableSharedFlow()
val viewModel =
CourseUnitContainerViewModel("", "", config, interactor, notifier, analytics)
CourseUnitContainerViewModel(
"",
"",
config,
interactor,
notifier,
analytics,
corePreferences
)

coEvery { interactor.getCourseStructure(any()) } throws UnknownHostException()
coEvery { interactor.getCourseStructureForVideos(any()) } throws UnknownHostException()
Expand All @@ -211,7 +229,15 @@ class CourseUnitContainerViewModelTest {
fun `getBlocks unknown success`() = runTest {
every { notifier.notifier } returns MutableSharedFlow()
val viewModel =
CourseUnitContainerViewModel("", "", config, interactor, notifier, analytics)
CourseUnitContainerViewModel(
"",
"",
config,
interactor,
notifier,
analytics,
corePreferences
)

coEvery { interactor.getCourseStructure(any()) } returns courseStructure
coEvery { interactor.getCourseStructureForVideos(any()) } returns courseStructure
Expand All @@ -228,7 +254,15 @@ class CourseUnitContainerViewModelTest {
fun setupCurrentIndex() = runTest {
every { notifier.notifier } returns MutableSharedFlow()
val viewModel =
CourseUnitContainerViewModel("", "", config, interactor, notifier, analytics)
CourseUnitContainerViewModel(
"",
"",
config,
interactor,
notifier,
analytics,
corePreferences
)
coEvery { interactor.getCourseStructure(any()) } returns courseStructure
coEvery { interactor.getCourseStructureForVideos(any()) } returns courseStructure

Expand All @@ -243,7 +277,15 @@ class CourseUnitContainerViewModelTest {
fun `getCurrentBlock test`() = runTest {
every { notifier.notifier } returns MutableSharedFlow()
val viewModel =
CourseUnitContainerViewModel("", "", config, interactor, notifier, analytics)
CourseUnitContainerViewModel(
"",
"",
config,
interactor,
notifier,
analytics,
corePreferences
)
coEvery { interactor.getCourseStructure(any()) } returns courseStructure
coEvery { interactor.getCourseStructureForVideos(any()) } returns courseStructure

Expand All @@ -260,7 +302,15 @@ class CourseUnitContainerViewModelTest {
fun `moveToPrevBlock null`() = runTest {
every { notifier.notifier } returns MutableSharedFlow()
val viewModel =
CourseUnitContainerViewModel("", "", config, interactor, notifier, analytics)
CourseUnitContainerViewModel(
"",
"",
config,
interactor,
notifier,
analytics,
corePreferences
)
coEvery { interactor.getCourseStructure(any()) } returns courseStructure
coEvery { interactor.getCourseStructureForVideos(any()) } returns courseStructure

Expand All @@ -277,7 +327,15 @@ class CourseUnitContainerViewModelTest {
fun `moveToPrevBlock not null`() = runTest {
every { notifier.notifier } returns MutableSharedFlow()
val viewModel =
CourseUnitContainerViewModel("", "id", config, interactor, notifier, analytics)
CourseUnitContainerViewModel(
"",
"id",
config,
interactor,
notifier,
analytics,
corePreferences
)
coEvery { interactor.getCourseStructure(any()) } returns courseStructure
coEvery { interactor.getCourseStructureForVideos(any()) } returns courseStructure

Expand All @@ -294,7 +352,15 @@ class CourseUnitContainerViewModelTest {
fun `moveToNextBlock null`() = runTest {
every { notifier.notifier } returns MutableSharedFlow()
val viewModel =
CourseUnitContainerViewModel("", "", config, interactor, notifier, analytics)
CourseUnitContainerViewModel(
"",
"",
config,
interactor,
notifier,
analytics,
corePreferences
)
coEvery { interactor.getCourseStructure(any()) } returns courseStructure
coEvery { interactor.getCourseStructureForVideos(any()) } returns courseStructure

Expand All @@ -311,7 +377,15 @@ class CourseUnitContainerViewModelTest {
fun `moveToNextBlock not null`() = runTest {
every { notifier.notifier } returns MutableSharedFlow()
val viewModel =
CourseUnitContainerViewModel("", "id", config, interactor, notifier, analytics)
CourseUnitContainerViewModel(
"",
"id",
config,
interactor,
notifier,
analytics,
corePreferences
)
coEvery { interactor.getCourseStructure("") } returns courseStructure
coEvery { interactor.getCourseStructureForVideos("") } returns courseStructure

Expand All @@ -328,7 +402,15 @@ class CourseUnitContainerViewModelTest {
fun `currentIndex isLastIndex`() = runTest {
every { notifier.notifier } returns MutableSharedFlow()
val viewModel =
CourseUnitContainerViewModel("", "", config, interactor, notifier, analytics)
CourseUnitContainerViewModel(
"",
"",
config,
interactor,
notifier,
analytics,
corePreferences
)
coEvery { interactor.getCourseStructure(any()) } returns courseStructure
coEvery { interactor.getCourseStructureForVideos(any()) } returns courseStructure

Expand Down
Loading