Skip to content

Commit a7073d4

Browse files
feat: changes according PR feedback
1 parent 19ce672 commit a7073d4

File tree

14 files changed

+143
-44
lines changed

14 files changed

+143
-44
lines changed

app/src/main/java/org/openedx/app/di/ScreenModule.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,14 +336,16 @@ val screenModule = module {
336336
get(),
337337
get(),
338338
get(),
339+
get(),
339340
)
340341
}
341342
viewModel { (courseId: String) -> BaseVideoViewModel(courseId, get()) }
342343
viewModel { (courseId: String) -> VideoViewModel(courseId, get(), get(), get(), get()) }
343-
viewModel { (courseId: String, videoUrl: String) ->
344+
viewModel { (courseId: String, videoUrl: String, blockId: String) ->
344345
VideoUnitViewModel(
345346
courseId,
346347
videoUrl,
348+
blockId,
347349
get(),
348350
get(),
349351
get(),

core/src/main/java/org/openedx/core/data/model/room/VideoProgressEntity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import androidx.room.PrimaryKey
77
@Entity(tableName = "video_progress_table")
88
data class VideoProgressEntity(
99
@PrimaryKey
10+
@ColumnInfo("block_id")
11+
val blockId: String,
1012
@ColumnInfo("video_url")
1113
val videoUrl: String,
1214
@ColumnInfo("video_time")

core/src/main/java/org/openedx/core/data/storage/CourseDao.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ interface CourseDao {
3636
@Insert(onConflict = OnConflictStrategy.REPLACE)
3737
suspend fun insertVideoProgressEntity(vararg videoProgressEntity: VideoProgressEntity)
3838

39-
@Query("SELECT * FROM video_progress_table WHERE video_url=:videoUrl")
40-
suspend fun getVideoProgressByVideoUrl(videoUrl: String): VideoProgressEntity?
39+
@Query("SELECT * FROM video_progress_table WHERE block_id=:blockId")
40+
suspend fun getVideoProgressByBlockId(blockId: String): VideoProgressEntity?
4141

4242
@Insert(onConflict = OnConflictStrategy.REPLACE)
4343
suspend fun insertCourseProgressEntity(vararg courseProgressEntity: CourseProgressEntity)

core/src/main/java/org/openedx/core/domain/model/Block.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.openedx.core.domain.model
22

3+
import android.content.Context
34
import android.os.Parcelable
45
import android.webkit.URLUtil
56
import kotlinx.parcelize.Parcelize
@@ -9,6 +10,7 @@ import org.openedx.core.BlockType
910
import org.openedx.core.module.db.DownloadModel
1011
import org.openedx.core.module.db.FileType
1112
import org.openedx.core.utils.PreviewHelper
13+
import org.openedx.core.utils.VideoPreview
1214
import org.openedx.core.utils.VideoUtil
1315
import java.util.Date
1416

@@ -82,21 +84,24 @@ data class Block(
8284
}
8385
}
8486

85-
fun getVideoPreview(isOnline: Boolean, offlineUrl: String?): Any? {
87+
fun getVideoPreview(context: Context, isOnline: Boolean, offlineUrl: String?): VideoPreview? {
8688
return if (studentViewData?.encodedVideos?.hasYoutubeUrl == true) {
87-
PreviewHelper.getYouTubeThumbnailUrl(
88-
studentViewData.encodedVideos.youtube?.url ?: ""
89+
val youtubeUrl = studentViewData.encodedVideos.youtube?.url ?: ""
90+
VideoPreview.createYoutubePreview(
91+
PreviewHelper.getYouTubeThumbnailUrl(youtubeUrl)
8992
)
9093
} else if (studentViewData?.encodedVideos?.hasVideoUrl == true) {
9194
val videoUrl = if (studentViewData.encodedVideos.videoUrl.isNotEmpty() && isOnline) {
9295
studentViewData.encodedVideos.videoUrl
9396
} else {
9497
offlineUrl ?: ""
9598
}
96-
PreviewHelper.getVideoFrameBitmap(
99+
val bitmap = PreviewHelper.getVideoFrameBitmap(
100+
context = context,
97101
isOnline = isOnline,
98102
videoUrl = videoUrl
99103
)
104+
bitmap?.let { VideoPreview.createEncodedVideoPreview(it) }
100105
} else {
101106
null
102107
}

core/src/main/java/org/openedx/core/utils/PreviewHelper.kt

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
11
package org.openedx.core.utils
22

3+
import android.content.Context
34
import android.graphics.Bitmap
5+
import android.graphics.BitmapFactory
46
import android.media.MediaMetadataRetriever
7+
import java.io.File
8+
import java.io.FileOutputStream
9+
import java.security.MessageDigest
10+
11+
data class VideoPreview(
12+
val link: String? = null,
13+
val bitmap: Bitmap? = null
14+
) {
15+
companion object {
16+
fun createYoutubePreview(link: String): VideoPreview {
17+
return VideoPreview(link = link)
18+
}
19+
20+
fun createEncodedVideoPreview(bitmap: Bitmap): VideoPreview {
21+
return VideoPreview(bitmap = bitmap)
22+
}
23+
}
24+
}
525

626
object PreviewHelper {
727

@@ -20,19 +40,37 @@ object PreviewHelper {
2040
return matchResult?.groups?.get(1)?.value ?: ""
2141
}
2242

23-
fun getVideoFrameBitmap(isOnline: Boolean, videoUrl: String): Bitmap? {
43+
fun getVideoFrameBitmap(context: Context, isOnline: Boolean, videoUrl: String): Bitmap? {
2444
if (!isOnline && !isLocalFile(videoUrl)) {
2545
return null
2646
}
2747

48+
// Check cache first
49+
val cacheFile = getCacheFile(context, videoUrl)
50+
if (cacheFile.exists()) {
51+
return try {
52+
BitmapFactory.decodeFile(cacheFile.absolutePath)
53+
} catch (e: Exception) {
54+
e.printStackTrace()
55+
null
56+
}
57+
}
58+
2859
val retriever = MediaMetadataRetriever()
2960
return try {
3061
if (isLocalFile(videoUrl)) {
3162
retriever.setDataSource(videoUrl)
3263
} else {
3364
retriever.setDataSource(videoUrl, HashMap())
3465
}
35-
retriever.getFrameAtTime(0)
66+
val bitmap = retriever.getFrameAtTime(0)
67+
68+
// Save bitmap to cache if it was successfully retrieved
69+
bitmap?.let {
70+
saveBitmapToCache(context, videoUrl, it)
71+
}
72+
73+
bitmap
3674
} catch (e: Exception) {
3775
// Log the exception for debugging but don't crash
3876
e.printStackTrace()
@@ -50,4 +88,57 @@ object PreviewHelper {
5088
private fun isLocalFile(url: String): Boolean {
5189
return url.startsWith("/") || url.startsWith("file://")
5290
}
91+
92+
private fun getCacheFile(context: Context, videoUrl: String): File {
93+
val cacheDir = context.cacheDir
94+
val fileName = generateFileName(videoUrl)
95+
return File(cacheDir, "video_thumbnails/$fileName")
96+
}
97+
98+
private fun generateFileName(videoUrl: String): String {
99+
val md = MessageDigest.getInstance("MD5")
100+
val digest = md.digest(videoUrl.toByteArray())
101+
return digest.joinToString("") { "%02x".format(it) } + ".jpg"
102+
}
103+
104+
private fun saveBitmapToCache(context: Context, videoUrl: String, bitmap: Bitmap) {
105+
try {
106+
val cacheFile = getCacheFile(context, videoUrl)
107+
cacheFile.parentFile?.mkdirs() // Create directories if they don't exist
108+
109+
FileOutputStream(cacheFile).use { out ->
110+
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)
111+
}
112+
} catch (e: Exception) {
113+
e.printStackTrace()
114+
}
115+
}
116+
117+
/**
118+
* Clear the bitmap cache to free storage
119+
*/
120+
fun clearCache(context: Context) {
121+
try {
122+
val cacheDir = File(context.cacheDir, "video_thumbnails")
123+
if (cacheDir.exists()) {
124+
cacheDir.deleteRecursively()
125+
}
126+
} catch (e: Exception) {
127+
e.printStackTrace()
128+
}
129+
}
130+
131+
/**
132+
* Remove a specific bitmap from cache
133+
*/
134+
fun removeFromCache(context: Context, videoUrl: String) {
135+
try {
136+
val cacheFile = getCacheFile(context, videoUrl)
137+
if (cacheFile.exists()) {
138+
cacheFile.delete()
139+
}
140+
} catch (e: Exception) {
141+
e.printStackTrace()
142+
}
143+
}
53144
}

course/src/main/java/org/openedx/course/data/repository/CourseRepository.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,14 +241,19 @@ class CourseRepository(
241241
}
242242
}
243243

244-
suspend fun saveVideoProgress(videoUrl: String, videoTime: Long, duration: Long) {
245-
val videoProgressEntity = VideoProgressEntity(videoUrl, videoTime, duration)
244+
suspend fun saveVideoProgress(
245+
blockId: String,
246+
videoUrl: String,
247+
videoTime: Long,
248+
duration: Long
249+
) {
250+
val videoProgressEntity = VideoProgressEntity(blockId, videoUrl, videoTime, duration)
246251
courseDao.insertVideoProgressEntity(videoProgressEntity)
247252
}
248253

249-
suspend fun getVideoProgress(videoUrl: String): VideoProgressEntity {
250-
return courseDao.getVideoProgressByVideoUrl(videoUrl)
251-
?: VideoProgressEntity(videoUrl, 0L, 0L)
254+
suspend fun getVideoProgress(blockId: String): VideoProgressEntity {
255+
return courseDao.getVideoProgressByBlockId(blockId)
256+
?: VideoProgressEntity(blockId, "", 0L, 0L)
252257
}
253258

254259
fun getCourseProgress(courseId: String, isRefresh: Boolean): Flow<CourseProgress> =

course/src/main/java/org/openedx/course/domain/interactor/CourseInteractor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class CourseInteractor(
115115
suspend fun submitOfflineXBlockProgress(blockId: String, courseId: String) =
116116
repository.submitOfflineXBlockProgress(blockId, courseId)
117117

118-
suspend fun getVideoProgress(videoUrl: String) = repository.getVideoProgress(videoUrl)
118+
suspend fun getVideoProgress(blockId: String) = repository.getVideoProgress(blockId)
119119

120120
fun getCourseProgress(courseId: String, isRefresh: Boolean) =
121121
repository.getCourseProgress(courseId, isRefresh)

course/src/main/java/org/openedx/course/presentation/assignments/CourseAssignmentViewModel.kt

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ import org.openedx.core.system.notifier.CourseNotifier
1515
import org.openedx.core.system.notifier.CourseStructureUpdated
1616
import org.openedx.course.domain.interactor.CourseInteractor
1717
import org.openedx.course.presentation.CourseAnalytics
18-
import org.openedx.course.presentation.CourseAnalyticsEvent
19-
import org.openedx.course.presentation.CourseAnalyticsKey
2018
import org.openedx.course.presentation.CourseRouter
21-
import org.openedx.course.presentation.outline.CourseContentAllUIState
2219

2320
class CourseAssignmentViewModel(
2421
val courseId: String,
@@ -96,22 +93,4 @@ class CourseAssignmentViewModel(
9693
}
9794
}
9895
}
99-
100-
fun navigateToSequentialEvent(blockId: String) {
101-
val currentState = uiState.value
102-
if (currentState is CourseContentAllUIState.CourseData) {
103-
analytics.logEvent(
104-
CourseAnalyticsEvent.ASSIGNMENT_CLICKED.eventName,
105-
buildMap {
106-
put(
107-
CourseAnalyticsKey.NAME.key,
108-
CourseAnalyticsEvent.ASSIGNMENT_CLICKED.biValue
109-
)
110-
put(CourseAnalyticsKey.COURSE_ID.key, courseId)
111-
put(CourseAnalyticsKey.COURSE_NAME.key, courseName)
112-
put(CourseAnalyticsKey.BLOCK_ID.key, blockId)
113-
}
114-
)
115-
}
116-
}
11796
}

course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ import org.openedx.core.ui.theme.appColors
110110
import org.openedx.core.ui.theme.appShapes
111111
import org.openedx.core.ui.theme.appTypography
112112
import org.openedx.core.utils.TimeUtils
113+
import org.openedx.core.utils.VideoPreview
113114
import org.openedx.course.R
114115
import org.openedx.course.presentation.dates.mockedCourseBannerInfo
115116
import org.openedx.course.presentation.outline.getUnitBlockIcon
@@ -612,7 +613,7 @@ fun VideoSubtitles(
612613
fun CourseVideoSection(
613614
block: Block,
614615
videoBlocks: List<Block>,
615-
preview: Map<String, Any?>,
616+
preview: Map<String, VideoPreview?>,
616617
progress: Map<String, Float>,
617618
downloadedStateMap: Map<String, DownloadedState>,
618619
onVideoClick: (Block) -> Unit,
@@ -676,7 +677,7 @@ fun CourseVideoSection(
676677
@Composable
677678
fun CourseVideoItem(
678679
videoBlock: Block,
679-
preview: Any?,
680+
preview: VideoPreview?,
680681
progress: Float,
681682
onClick: () -> Unit
682683
) {
@@ -702,7 +703,7 @@ fun CourseVideoItem(
702703
modifier = Modifier
703704
.fillMaxSize(),
704705
model = ImageRequest.Builder(LocalContext.current)
705-
.data(preview)
706+
.data(preview?.link ?: preview?.bitmap)
706707
.error(coreR.drawable.core_no_image_course)
707708
.placeholder(coreR.drawable.core_no_image_course)
708709
.build(),

course/src/main/java/org/openedx/course/presentation/unit/video/EncodedVideoUnitViewModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import java.util.concurrent.Executors
3333
class EncodedVideoUnitViewModel(
3434
courseId: String,
3535
videoUrl: String,
36-
val blockId: String,
36+
blockId: String,
3737
private val context: Context,
3838
private val preferencesManager: CorePreferences,
3939
courseRepository: CourseRepository,
@@ -44,6 +44,7 @@ class EncodedVideoUnitViewModel(
4444
) : VideoUnitViewModel(
4545
courseId,
4646
videoUrl,
47+
blockId,
4748
courseRepository,
4849
notifier,
4950
networkConnection,

0 commit comments

Comments
 (0)