Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class FileBrowserViewModel(
viewModelScope.launch {
appModelObservable.value = appModelMapper.map(state = state, loading = true)
state.sourceNode?.apply {
// fixme when this is possible on both platforms - need a broader check
if (cuerCastPlayerWatcher.isWatching()) {
launchRemotePlayer(
cuerCastPlayerWatcher.remoteNode ?: throw IllegalStateException("No remote"),
Expand All @@ -101,7 +102,12 @@ class FileBrowserViewModel(
state.selectedFile ?: throw IllegalStateException(),
screen.index
)
castController.connectCuerCast(state.sourceNode, screen)
// todo check if already connected to remote node
// todo also check if the dialog has connected
if (!castController.isConnected()) {
// assumes here that sourecnode == targetnode
castController.connectCuerCast(state.sourceNode, screen)
}
remoteDialogLauncher.hideRemotesDialog()
appModelObservable.value = appModelMapper.map(state = state, loading = false)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@
log.d(command.toString())
when (command) {
is PlayerContract.PlayerCommand.Load -> {
log.d("PlayerCommand.Load: $_currentVideoId != ${command.platformId} start:${command.startPosition}")
if (_currentVideoId != command.platformId) {
_player?.loadVideo(command.platformId, command.startPosition / 1000f)
log.d("PlayerCommand.Load: $_currentVideoId != ${command.item.media.platformId} start:${command.startPosition}")

Check warning

Code scanning / detekt

Line detected that is longer than the defined maximum line length in the code style. Warning

Line detected that is longer than the defined maximum line length in the code style.
if (_currentVideoId != command.item.media.platformId) {
_player?.loadVideo(command.item.media.platformId, command.startPosition / 1000f)

Check warning

Code scanning / detekt

Report magic numbers. Magic number is a numeric literal that is not defined as a constant and hence it's unclear what the purpose of this number is. It's better to declare such numbers as constants and give them a proper name. By default, -1, 0, 1, and 2 are not considered to be magic numbers. Warning

This expression contains a magic number. Consider defining it to a well named constant.
_progressBar?.isVisible = true
} else {
_player?.play()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class YoutubeFullScreenActivity : YouTubeBaseActivity(),
when (label) {
is Command -> label.command.let { command ->
when (command) {
is Load -> player.cueVideo(command.platformId, command.startPosition.toInt())
is Load -> player.cueVideo(command.item.media.platformId, command.startPosition.toInt())
is Play -> player.play()
is Pause -> player.pause()
is SkipBack -> player.seekToMillis(player.currentTimeMillis - command.ms)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ fun LocalNodeDomain.locator() = Locator(ipAddress, port)
fun Pair<String, Int>.http() = "http://$first:$second"
fun LocalNodeDomain.http() = "http://$ipAddress:$port"
fun RemoteNodeDomain.http() = "http://$ipAddress:$port"
fun Locator.http() = "http://$address:$port"

fun Pair<String, Int>.https() = "https://$first:$second"
fun LocalNodeDomain.https() = "https://$ipAddress:$port"
fun RemoteNodeDomain.https() = "https://$ipAddress:$port"
fun Locator.https() = "https://$address:$port"
1 change: 1 addition & 0 deletions hub/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies {
implementation(libs.kotlinxCoroutinesJdk8)
implementation(libs.ktorClientCore)
implementation(libs.ktorClientCio)
implementation(libs.kotlinxDatetime)
implementation(libs.batikTranscoder)
implementation(libs.multiplatformSettings)
implementation(libs.vlcj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import uk.co.caprica.vlcj.player.base.MediaPlayerEventAdapter
import uk.co.caprica.vlcj.player.base.State
import uk.co.caprica.vlcj.player.component.CallbackMediaPlayerComponent
import uk.co.sentinelweb.cuer.app.orchestrator.OrchestratorContract.Source.REMOTE
import uk.co.sentinelweb.cuer.app.ui.player.PlayerContract
import uk.co.sentinelweb.cuer.app.ui.player.PlayerContract.PlayerCommand.*
import uk.co.sentinelweb.cuer.app.ui.player.PlayerContract.View.Event.*
Expand All @@ -23,9 +24,14 @@
import uk.co.sentinelweb.cuer.core.providers.PlayerConfigProvider
import uk.co.sentinelweb.cuer.core.providers.TimeProvider
import uk.co.sentinelweb.cuer.core.wrapper.LogWrapper
import uk.co.sentinelweb.cuer.domain.MediaDomain.MediaTypeDomain.FILE
import uk.co.sentinelweb.cuer.domain.PlayerNodeDomain
import uk.co.sentinelweb.cuer.domain.PlayerStateDomain
import uk.co.sentinelweb.cuer.domain.PlayerStateDomain.*
import uk.co.sentinelweb.cuer.domain.PlaylistItemDomain
import uk.co.sentinelweb.cuer.remote.server.LocalRepository
import uk.co.sentinelweb.cuer.remote.server.http
import uk.co.sentinelweb.cuer.remote.server.locator
import java.awt.BorderLayout
import java.awt.BorderLayout.*
import java.awt.Color
Expand All @@ -42,6 +48,7 @@
private val folderListUseCase: GetFolderListUseCase,
private val showHideControls: VlcPlayerShowHideControls,
private val keyMap: VlcPlayerKeyMap,
private val localRepository: LocalRepository,
) : JFrame(), KoinComponent {

lateinit var mediaPlayerComponent: CallbackMediaPlayerComponent
Expand Down Expand Up @@ -395,11 +402,11 @@

fun playStateChanged(command: PlayerContract.PlayerCommand) = when (command) {
is Load -> {
command.platformId
.also { log.d("") }
.let { folderListUseCase.truncatedToFullFolderPath(it) }
command.item
.also { log.d("${it.media.platformId}") }
.let { mapPath(it) }
?.also { playItem(it) }
?: log.d("Cannot get full path ${command.platformId}")
?: log.d("Cannot get full path ${command.item.media.platformId}")
}

is Pause -> mediaPlayerComponent.mediaPlayer().controls().pause()
Expand All @@ -409,6 +416,16 @@
is SeekTo -> mediaPlayerComponent.mediaPlayer().controls().setTime(command.ms)
}.also { log.d("command:${command::class.java.simpleName}") }

private fun mapPath(item: PlaylistItemDomain) =
item
.takeIf { it.id != null && it.id?.source == REMOTE && it.id?.locator != null }
?.takeIf { localRepository.localNode.locator() != it.id?.locator }
?.takeIf { it.media.mediaType == FILE }
?.let { it.copy(media = it.media.copy(platformId = "${it.id?.locator?.http()}/video-stream/${it.media.platformId}")) }
?.media
?.platformId
?: folderListUseCase.truncatedToFullFolderPath(item.media.platformId)

fun updateTexts(texts: PlayerContract.View.Model.Texts) {
if (!this@VlcPlayerSwingWindow.isUndecorated) {
title = texts.title
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ class VlcPlayerUiCoordinator(
coordinator = get(),
folderListUseCase = get(),
showHideControls = VlcPlayerShowHideControls(),
keyMap = VlcPlayerKeyMap()
keyMap = VlcPlayerKeyMap(),
localRepository = get()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.utils.io.*

Check warning

Code scanning / detekt

Wildcard imports should be replaced with imports using fully qualified class names. Wildcard imports can lead to naming conflicts. A library update can introduce naming clashes with your classes which results in compilation errors. Warning

io.ktor.utils.io.* is a wildcard import. Replace it with fully qualified imports.
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import uk.co.sentinelweb.cuer.app.orchestrator.OrchestratorContract.Source
Expand Down Expand Up @@ -49,6 +52,7 @@
import uk.co.sentinelweb.cuer.remote.server.player.PlayerSessionContract.PlayerCommandMessage.*
import uk.co.sentinelweb.cuer.remote.server.player.PlayerSessionHolder
import uk.co.sentinelweb.cuer.remote.server.player.PlayerSessionMessageMapper
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter

Expand Down Expand Up @@ -302,6 +306,7 @@
try {
(postData)
.let { deserialisePlaylistItem(it) }
// .let {it.copy(media = it.media.copy())}
.let { item ->
remotePlayerLaunchHost.launchVideo(
item,
Expand All @@ -327,6 +332,48 @@
call.respond(HttpStatusCode.NotFound, "No folder with path: $path")
}
}
//http://192.168.1.12:9843/video-stream/torrent:::::farscape-s1:::::Farscape%20S01E03%20Back%20and%20Back%20and%20Back%20to%20the%20Future.mp4
get("/video-stream/{filePath}") {
val filePath = call.parameters["filePath"]
// ?.substring("/video-stream".length)
?.replace(":::::","/")
if (filePath == null) {
call.respond(HttpStatusCode.BadRequest, "File filePath is missing")
return@get
}
logWrapper.d("/video-stream/filepath: $filePath")

val parentPath = filePath.substring(0, filePath.lastIndexOf("/"))
val item = getFolderListUseCase.getFolderList(parentPath)
?.children?.filter { it.platformId?.endsWith(filePath)?:false }
val fullPath = getFolderListUseCase.truncatedToFullFolderPath(filePath)
logWrapper.d("/video-stream/filepath: $fullPath")

val file = File(fullPath)
if (!file.exists()) {
call.respond(HttpStatusCode.NotFound, "File not found")
return@get
}

val byteRange = call.request.header(HttpHeaders.Range)
if (byteRange != null) {
val range = parseRange(byteRange, file.length())
if (range != null) {
val (start, end) = range
call.response.header(HttpHeaders.ContentRange, "bytes $start-$end/${file.length()}")
call.respondBytesWriter(
status = HttpStatusCode.PartialContent,
contentType = ContentType.defaultForFile(file)
) {
writeFilePart(file, start, end)
}
} else {
call.respond(HttpStatusCode.RequestedRangeNotSatisfiable)
}
} else {
call.respondFile(file)
}
}
static("/") {
resources("")
}
Expand All @@ -352,3 +399,26 @@
ContentType.Application.Json
)
}


fun parseRange(rangeHeader: String, fileLength: Long): Pair<Long, Long>? {
val range = rangeHeader.removePrefix("bytes=").split("-")
val start = range[0].toLongOrNull() ?: return null
val end = range.getOrNull(1)?.toLongOrNull() ?: (fileLength - 1)
return if (start <= end) start to end else null
}

suspend fun ByteWriteChannel.writeFilePart(file: File, start: Long, end: Long) =
withContext(Dispatchers.IO) {
file.inputStream().use { inputStream ->
inputStream.skip(start)
var remaining = end - start + 1
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
while (remaining > 0) {
val read = inputStream.read(buffer, 0, minOf(buffer.size.toLong(), remaining).toInt())
if (read == -1) break
writeFully(buffer, 0, read)
remaining -= read
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ class CastController(
}
}

fun isConnected(): Boolean =
cuerCastPlayerWatcher.isWatching() || chromeCastHolder.isConnected()

fun killCurrentSession() {
chromeCastHolder.destroy()
cuerCastPlayerWatcher.cleanup()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ interface PlayerContract {
sealed class PlayerCommand {
object Play : PlayerCommand()
object Pause : PlayerCommand()
data class Load(val platformId: String, val startPosition: Long) : PlayerCommand()
data class Load(val item: PlaylistItemDomain, val startPosition: Long) : PlayerCommand()
data class SkipFwd(val ms: Int) : PlayerCommand()
data class SkipBack(val ms: Int) : PlayerCommand()
data class SeekTo(val ms: Long) : PlayerCommand()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ class PlayerStoreFactory(
dispatch(Result.SetVideo(intent.item, queueConsumer.playlist))
publish(
Label.Command(
Load(intent.item.media.platformId, intent.item.media.startPosition())
Load(intent.item, intent.item.media.startPosition())
)
)
}
Expand Down Expand Up @@ -286,7 +286,7 @@ class PlayerStoreFactory(
when (playState) {
VIDEO_CUED -> {
item?.media?.apply {
publish(Label.Command(Load(platformId, startPosition())))
publish(Label.Command(Load(item, startPosition())))
}
Unit
}
Expand Down
Loading