Skip to content

Commit

Permalink
Add ktor ByteChannel Support for audio (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
LuftVerbot authored Sep 17, 2024
1 parent ada193b commit 319e104
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class Downloader(

val id = when (val audio = media.audio) {
is Streamable.Audio.ByteStream -> TODO()
is Streamable.Audio.Channel -> TODO()
is Streamable.Audio.Http -> {
val request = audio.request
val downloadRequest = DownloadManager.Request(request.url.toUri()).apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import dev.brahmkshatriya.echo.playback.AudioResolver.Companion.copy
class AudioDataSource(
private val defaultDataSourceFactory: DefaultDataSource.Factory,
private val byteStreamDataSourceFactory: ByteStreamDataSource.Factory,
private val byteChannelDataSourceFactory: ByteChannelDataSource.Factory,
) : BaseDataSource(true) {

class Factory(
Expand All @@ -22,8 +23,9 @@ class AudioDataSource(

private val defaultDataSourceFactory = DefaultDataSource.Factory(context)
private val byteStreamDataSourceFactory = ByteStreamDataSource.Factory()
private val byteChannelDataSourceFactory = ByteChannelDataSource.Factory()
override fun createDataSource() =
AudioDataSource(defaultDataSourceFactory, byteStreamDataSourceFactory)
AudioDataSource(defaultDataSourceFactory, byteStreamDataSourceFactory, byteChannelDataSourceFactory)
}

private var source: DataSource? = null
Expand All @@ -46,6 +48,11 @@ class AudioDataSource(
byteStreamDataSourceFactory.createDataSource() to spec
}

is Streamable.Audio.Channel -> {
val spec = dataSpec.copy(customData = audio)
byteChannelDataSourceFactory.createDataSource() to spec
}

is Streamable.Audio.Http -> {
val spec = audio.request.run {
dataSpec.copy(uri = url.toUri(), httpRequestHeaders = headers)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package dev.brahmkshatriya.echo.playback

import androidx.annotation.OptIn
import androidx.core.net.toUri
import androidx.media3.common.C
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.BaseDataSource
import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DataSpec
import dev.brahmkshatriya.echo.common.models.Streamable
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.cancel
import kotlinx.coroutines.runBlocking
import java.io.IOException

@OptIn(UnstableApi::class)
class ByteChannelDataSource : BaseDataSource(true) {

class Factory : DataSource.Factory {
override fun createDataSource() = ByteChannelDataSource()
}

private var audio: Streamable.Audio.Channel? = null

override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
val channel = audio!!.channel
return runBlocking {
val bytesRead = channel.readAvailable(buffer, offset, length)
if (bytesRead == -1) C.RESULT_END_OF_INPUT else bytesRead
}
}

override fun open(dataSpec: DataSpec): Long {
val audio = dataSpec.customData as Streamable.Audio.Channel
val requestedPosition = dataSpec.position
runBlocking {
audio.channel.seek(requestedPosition)
}
this.audio = audio
return audio.totalBytes
}

override fun getUri() = audio?.hashCode().toString().toUri()

override fun close() {
runBlocking {
audio?.channel?.cancel()
}
audio = null
}

private suspend fun ByteReadChannel.seek(requestedPosition: Long) {
val discarded = discard(requestedPosition)
if (discarded < requestedPosition) {
throw IOException("Reached end of stream before desired position")
}
}
}
1 change: 1 addition & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ kotlin {

dependencies {
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
api("io.ktor:ktor-utils:2.3.0")
}

publishing {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.brahmkshatriya.echo.common.models

import dev.brahmkshatriya.echo.common.models.Request.Companion.toRequest
import io.ktor.utils.io.ByteReadChannel
import kotlinx.serialization.Serializable
import java.io.InputStream

Expand Down Expand Up @@ -68,6 +69,10 @@ data class Streamable(
val stream: InputStream, val totalBytes: Long, override val skipSilence: Boolean? = null
) : Audio()

data class Channel(
val channel: ByteReadChannel, val totalBytes: Long, override val skipSilence: Boolean? = null
) : Audio()

companion object {
fun String.toAudio(headers: Map<String, String> = mapOf()) =
Http(this.toRequest(headers))
Expand Down

0 comments on commit 319e104

Please sign in to comment.