diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 788f51d..fe58cf2 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -10,13 +10,15 @@ plugins { repositories { mavenCentral() - jcenter() maven("https://jitpack.io") } dependencies { testImplementation(group = "org.jetbrains.kotlin", name = "kotlin-test-junit5") testImplementation(group = "org.jetbrains.kotlin", name = "kotlin-test") + testImplementation(group = "ch.qos.logback", name = "logback-classic", version = "1.4.8") + + implementation(group = "org.jetbrains.kotlinx", name = "atomicfu", version = "0.21.0") implementation(group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version = "1.7.3") diff --git a/lib/src/main/kotlin/be/zvz/klover/container/matroska/MatroskaAudioTrack.kt b/lib/src/main/kotlin/be/zvz/klover/container/matroska/MatroskaAudioTrack.kt index 8a07a31..17a703a 100644 --- a/lib/src/main/kotlin/be/zvz/klover/container/matroska/MatroskaAudioTrack.kt +++ b/lib/src/main/kotlin/be/zvz/klover/container/matroska/MatroskaAudioTrack.kt @@ -35,7 +35,7 @@ class MatroskaAudioTrack(trackInfo: AudioTrackInfo, private val inputStream: See return try { val file = MatroskaStreamingFile(inputStream) file.readFile() - accurateDuration.set(file.duration.toInt().toLong()) + accurateDuration.value = file.duration.toLong() file } catch (e: IOException) { throw RuntimeException(e) diff --git a/lib/src/main/kotlin/be/zvz/klover/container/mpeg/MpegAudioTrack.kt b/lib/src/main/kotlin/be/zvz/klover/container/mpeg/MpegAudioTrack.kt index 0fedbe9..ee47155 100644 --- a/lib/src/main/kotlin/be/zvz/klover/container/mpeg/MpegAudioTrack.kt +++ b/lib/src/main/kotlin/be/zvz/klover/container/mpeg/MpegAudioTrack.kt @@ -25,7 +25,7 @@ open class MpegAudioTrack(trackInfo: AudioTrackInfo, private val inputStream: Se try { val fileReader = file.loadReader(trackConsumer) ?: throw FriendlyException("Unknown MP4 format.", FriendlyException.Severity.SUSPICIOUS, null) - accurateDuration.set(fileReader.duration) + accurateDuration.value = fileReader.duration localExecutor.executeProcessingLoop( { fileReader.provideFrames() }, { timecode: Long -> fileReader.seekToTimecode(timecode) }, diff --git a/lib/src/main/kotlin/be/zvz/klover/container/mpeg/MpegFileLoader.kt b/lib/src/main/kotlin/be/zvz/klover/container/mpeg/MpegFileLoader.kt index f2b86bd..edc3f82 100644 --- a/lib/src/main/kotlin/be/zvz/klover/container/mpeg/MpegFileLoader.kt +++ b/lib/src/main/kotlin/be/zvz/klover/container/mpeg/MpegFileLoader.kt @@ -8,9 +8,10 @@ import be.zvz.klover.container.mpeg.reader.MpegVersionedSectionInfo import be.zvz.klover.container.mpeg.reader.fragmented.MpegFragmentedFileTrackProvider import be.zvz.klover.container.mpeg.reader.standard.MpegStandardFileTrackProvider import be.zvz.klover.tools.io.SeekableInputStream +import kotlinx.atomicfu.AtomicBoolean +import kotlinx.atomicfu.atomic import java.io.IOException import java.util.Locale -import java.util.concurrent.atomic.AtomicBoolean /** * Handles processing an MP4 file for the purpose of streaming one specific track from it. Only performs seeks when @@ -53,9 +54,9 @@ class MpegFileLoader(inputStream: SeekableInputStream) { */ fun parseHeaders() { try { - val movieBoxSeen = AtomicBoolean() + val movieBoxSeen = atomic(false) reader.inChain(root).handle("moov") { moov: MpegSectionInfo -> - movieBoxSeen.set(true) + movieBoxSeen.value = true reader.inChain(moov).handle("trak") { trak: MpegSectionInfo -> parseTrackInfo(trak) } .handle("mvex") { mvex: MpegSectionInfo -> fragmentedFileReader.parseMovieExtended(mvex) } .handle("udta") { udta: MpegSectionInfo -> parseMetadata(udta) }.run() @@ -118,9 +119,9 @@ class MpegFileLoader(inputStream: SeekableInputStream) { if (!start && "sidx" == child.type) { true } else if (!start && "emsg" == child.type) { - movieBoxSeen.get() + movieBoxSeen.value } else if (start && ("mdat" == child.type || "free" == child.type)) { - movieBoxSeen.get() + movieBoxSeen.value } else { false } diff --git a/lib/src/main/kotlin/be/zvz/klover/container/mpeg/reader/fragmented/MpegFragmentedFileTrackProvider.kt b/lib/src/main/kotlin/be/zvz/klover/container/mpeg/reader/fragmented/MpegFragmentedFileTrackProvider.kt index 575cf25..29a1943 100644 --- a/lib/src/main/kotlin/be/zvz/klover/container/mpeg/reader/fragmented/MpegFragmentedFileTrackProvider.kt +++ b/lib/src/main/kotlin/be/zvz/klover/container/mpeg/reader/fragmented/MpegFragmentedFileTrackProvider.kt @@ -7,10 +7,10 @@ import be.zvz.klover.container.mpeg.reader.MpegSectionInfo import be.zvz.klover.container.mpeg.reader.MpegVersionedSectionInfo import be.zvz.klover.tools.Units import be.zvz.klover.tools.io.DetachedByteChannel +import kotlinx.atomicfu.atomic import java.io.IOException import java.nio.channels.Channels import java.nio.channels.ReadableByteChannel -import java.util.concurrent.atomic.AtomicReference /** * Track provider for fragmented MP4 file format. @@ -43,7 +43,7 @@ class MpegFragmentedFileTrackProvider(private val reader: MpegReader, private va reader.skip(moof) continue } - val fragment = parseTrackMovieFragment(moof, consumer.track.trackId) + val fragment = parseTrackMovieFragment(moof, consumer.track.trackId)!! val mdat = reader.nextChild(root) val timecode = fragment.baseTimecode reader.seek.seek(moof.offset + fragment.dataOffset) @@ -133,8 +133,8 @@ class MpegFragmentedFileTrackProvider(private val reader: MpegReader, private va } @Throws(IOException::class) - private fun parseTrackMovieFragment(moof: MpegSectionInfo, trackId: Int): MpegTrackFragmentHeader { - val header = AtomicReference() + private fun parseTrackMovieFragment(moof: MpegSectionInfo, trackId: Int): MpegTrackFragmentHeader? { + val header = atomic(null) reader.inChain(moof).handle("traf") { traf: MpegSectionInfo -> val builder = MpegTrackFragmentHeader.Builder() reader.inChain(traf) @@ -154,10 +154,10 @@ class MpegFragmentedFileTrackProvider(private val reader: MpegReader, private va } }.run() if (builder.trackId == trackId) { - header.set(builder.build()) + header.value = builder.build() } }.run() - return header.get() + return header.value } @Throws(IOException::class) diff --git a/lib/src/main/kotlin/be/zvz/klover/filter/AudioPipelineFactory.kt b/lib/src/main/kotlin/be/zvz/klover/filter/AudioPipelineFactory.kt index af8b021..08bbeac 100644 --- a/lib/src/main/kotlin/be/zvz/klover/filter/AudioPipelineFactory.kt +++ b/lib/src/main/kotlin/be/zvz/klover/filter/AudioPipelineFactory.kt @@ -15,7 +15,7 @@ object AudioPipelineFactory { */ @JvmStatic fun isProcessingRequired(context: AudioProcessingContext, inputFormat: AudioDataFormat?): Boolean { - return context.outputFormat != inputFormat || context.playerOptions.volumeLevel.get() != 100 || context.playerOptions.filterFactory.get() != null + return context.outputFormat != inputFormat || context.playerOptions.volumeLevel.value != 100 || context.playerOptions.filterFactory.value != null } /** @@ -32,7 +32,7 @@ object AudioPipelineFactory { val end: UniversalPcmAudioFilter = FinalPcmAudioFilter(context, createPostProcessors(context)) val builder = FilterChainBuilder() builder.addFirst(end) - if (context.filterHotSwapEnabled || context.playerOptions.filterFactory.get() != null) { + if (context.filterHotSwapEnabled || context.playerOptions.filterFactory.value != null) { val userFilters = UserProvidedAudioFilters(context, end) builder.addFirst(userFilters) } diff --git a/lib/src/main/kotlin/be/zvz/klover/filter/BufferingPostProcessor.kt b/lib/src/main/kotlin/be/zvz/klover/filter/BufferingPostProcessor.kt index 6496c0c..953b5c2 100644 --- a/lib/src/main/kotlin/be/zvz/klover/filter/BufferingPostProcessor.kt +++ b/lib/src/main/kotlin/be/zvz/klover/filter/BufferingPostProcessor.kt @@ -26,7 +26,7 @@ class BufferingPostProcessor(private val context: AudioProcessingContext, privat outputBuffer.clear() encoder.encode(buffer, outputBuffer) offeredFrame.timecode = timecode - offeredFrame.volume = context.playerOptions.volumeLevel.get() + offeredFrame.volume = context.playerOptions.volumeLevel.value offeredFrame.setBuffer(outputBuffer) context.frameBuffer.consume(offeredFrame) } diff --git a/lib/src/main/kotlin/be/zvz/klover/filter/UserProvidedAudioFilters.kt b/lib/src/main/kotlin/be/zvz/klover/filter/UserProvidedAudioFilters.kt index 8ab6335..7800ffb 100644 --- a/lib/src/main/kotlin/be/zvz/klover/filter/UserProvidedAudioFilters.kt +++ b/lib/src/main/kotlin/be/zvz/klover/filter/UserProvidedAudioFilters.kt @@ -50,7 +50,7 @@ class UserProvidedAudioFilters( @Throws(InterruptedException::class) private fun checkRebuild() { - if (hotSwapEnabled && context.playerOptions.filterFactory.get() !== chain.context) { + if (hotSwapEnabled && context.playerOptions.filterFactory.value !== chain.context) { flush() close() chain = buildFragment(context, nextFilter) @@ -62,7 +62,7 @@ class UserProvidedAudioFilters( context: AudioProcessingContext, nextFilter: UniversalPcmAudioFilter, ): AudioFilterChain { - val factory = context.playerOptions.filterFactory.get() + val factory = context.playerOptions.filterFactory.value return if (factory == null) { AudioFilterChain(nextFilter, emptyList(), null) } else { diff --git a/lib/src/main/kotlin/be/zvz/klover/filter/volume/AudioFrameVolumeChanger.kt b/lib/src/main/kotlin/be/zvz/klover/filter/volume/AudioFrameVolumeChanger.kt index 63ca828..51ebd7f 100644 --- a/lib/src/main/kotlin/be/zvz/klover/filter/volume/AudioFrameVolumeChanger.kt +++ b/lib/src/main/kotlin/be/zvz/klover/filter/volume/AudioFrameVolumeChanger.kt @@ -80,7 +80,7 @@ class AudioFrameVolumeChanger private constructor( val volumeChanger = AudioFrameVolumeChanger( context.configuration, context.outputFormat, - context.playerOptions.volumeLevel.get(), + context.playerOptions.volumeLevel.value, ) try { volumeChanger.setupLibraries() diff --git a/lib/src/main/kotlin/be/zvz/klover/filter/volume/VolumePostProcessor.kt b/lib/src/main/kotlin/be/zvz/klover/filter/volume/VolumePostProcessor.kt index 4dea970..a8bb9b7 100644 --- a/lib/src/main/kotlin/be/zvz/klover/filter/volume/VolumePostProcessor.kt +++ b/lib/src/main/kotlin/be/zvz/klover/filter/volume/VolumePostProcessor.kt @@ -8,11 +8,11 @@ import java.nio.ShortBuffer * Audio chunk post processor to apply selected volume. */ class VolumePostProcessor(private val context: AudioProcessingContext) : AudioPostProcessor { - private val volumeProcessor: PcmVolumeProcessor = PcmVolumeProcessor(context.playerOptions.volumeLevel.get()) + private val volumeProcessor: PcmVolumeProcessor = PcmVolumeProcessor(context.playerOptions.volumeLevel.value) @Throws(InterruptedException::class) override fun process(timecode: Long, buffer: ShortBuffer) { - val currentVolume = context.playerOptions.volumeLevel.get() + val currentVolume = context.playerOptions.volumeLevel.value if (currentVolume != volumeProcessor.lastVolume) { AudioFrameVolumeChanger.apply(context) } diff --git a/lib/src/main/kotlin/be/zvz/klover/natives/NativeResourceHolder.kt b/lib/src/main/kotlin/be/zvz/klover/natives/NativeResourceHolder.kt index 28a7dc8..6631305 100644 --- a/lib/src/main/kotlin/be/zvz/klover/natives/NativeResourceHolder.kt +++ b/lib/src/main/kotlin/be/zvz/klover/natives/NativeResourceHolder.kt @@ -1,18 +1,18 @@ package be.zvz.klover.natives -import java.util.concurrent.atomic.AtomicBoolean +import kotlinx.atomicfu.atomic /** * Abstract instance of a class which holds native resources that must be freed. */ abstract class NativeResourceHolder : AutoCloseable { - private val released = AtomicBoolean() + private val released = atomic(false) /** * Assert that the native resources have not been freed. */ protected fun checkNotReleased() { - check(!released.get()) { "Cannot use the decoder after closing it." } + check(!released.value) { "Cannot use the decoder after closing it." } } /** diff --git a/lib/src/main/kotlin/be/zvz/klover/player/AudioPlayerLifecycleManager.kt b/lib/src/main/kotlin/be/zvz/klover/player/AudioPlayerLifecycleManager.kt index a4baef2..e040adf 100644 --- a/lib/src/main/kotlin/be/zvz/klover/player/AudioPlayerLifecycleManager.kt +++ b/lib/src/main/kotlin/be/zvz/klover/player/AudioPlayerLifecycleManager.kt @@ -4,13 +4,14 @@ import be.zvz.klover.player.event.AudioEvent import be.zvz.klover.player.event.AudioEventListener import be.zvz.klover.player.event.TrackEndEvent import be.zvz.klover.player.event.TrackStartEvent +import kotlinx.atomicfu.AtomicLong +import kotlinx.atomicfu.AtomicRef +import kotlinx.atomicfu.atomic import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicLong -import java.util.concurrent.atomic.AtomicReference /** * Triggers cleanup checks on all active audio players at a fixed interval. @@ -22,7 +23,7 @@ class AudioPlayerLifecycleManager(private val scheduler: ScheduledExecutorServic Runnable, AudioEventListener { private val activePlayers: ConcurrentMap = ConcurrentHashMap() - private val scheduledTask: AtomicReference> = AtomicReference() + private val scheduledTask: AtomicRef?> = atomic(null) /** * Initialise the scheduled task. @@ -52,7 +53,7 @@ class AudioPlayerLifecycleManager(private val scheduler: ScheduledExecutorServic override fun run() { activePlayers.keys.forEach { player -> - player.checkCleanup(cleanupThreshold.get()) + player.checkCleanup(cleanupThreshold.value) } } diff --git a/lib/src/main/kotlin/be/zvz/klover/player/AudioPlayerOptions.kt b/lib/src/main/kotlin/be/zvz/klover/player/AudioPlayerOptions.kt index 295b5ee..897e039 100644 --- a/lib/src/main/kotlin/be/zvz/klover/player/AudioPlayerOptions.kt +++ b/lib/src/main/kotlin/be/zvz/klover/player/AudioPlayerOptions.kt @@ -1,8 +1,7 @@ package be.zvz.klover.player import be.zvz.klover.filter.PcmFilterFactory -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.atomic.AtomicReference +import kotlinx.atomicfu.atomic /** * Mutable options of an audio player which may be applied in real-time. @@ -11,16 +10,16 @@ class AudioPlayerOptions { /** * Volume level of the audio, see [AudioPlayer.volume]. Applied in real-time. */ - val volumeLevel: AtomicInteger = AtomicInteger(100) + val volumeLevel = atomic(100) /** * Current PCM filter factory. Applied in real-time. */ - val filterFactory: AtomicReference = AtomicReference() + val filterFactory = atomic(null) /** * Current frame buffer size. If not set, the global default is used. Changing this only affects the next track that * is started. */ - val frameBufferDuration: AtomicReference = AtomicReference() + val frameBufferDuration = atomic(-1) } diff --git a/lib/src/main/kotlin/be/zvz/klover/player/DefaultAudioPlayer.kt b/lib/src/main/kotlin/be/zvz/klover/player/DefaultAudioPlayer.kt index 1fcf162..157f78b 100644 --- a/lib/src/main/kotlin/be/zvz/klover/player/DefaultAudioPlayer.kt +++ b/lib/src/main/kotlin/be/zvz/klover/player/DefaultAudioPlayer.kt @@ -18,10 +18,10 @@ import be.zvz.klover.track.playback.AudioFrame import be.zvz.klover.track.playback.AudioTrackExecutor import be.zvz.klover.track.playback.LocalAudioTrackExecutor import be.zvz.klover.track.playback.MutableAudioFrame +import kotlinx.atomicfu.atomic import org.slf4j.LoggerFactory import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException -import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.Volatile import kotlin.concurrent.withLock @@ -48,7 +48,7 @@ class DefaultAudioPlayer(private val manager: DefaultAudioPlayerManager) : Audio @Volatile private var shadowTrack: InternalAudioTrack? = null - private val paused: AtomicBoolean = AtomicBoolean() + private val paused = atomic(false) private val listeners: MutableList = mutableListOf() private val trackSwitchLock: ReentrantLock = ReentrantLock() private val options: AudioPlayerOptions = AudioPlayerOptions() @@ -151,7 +151,7 @@ class DefaultAudioPlayer(private val manager: DefaultAudioPlayerManager) : Audio var track: InternalAudioTrack? = null lastRequestTime = System.currentTimeMillis() - if (timeout == 0L && paused.get()) { + if (timeout == 0L && paused.value) { return null } while (activeTrack?.also { track = it } != null) { @@ -188,7 +188,7 @@ class DefaultAudioPlayer(private val manager: DefaultAudioPlayerManager) : Audio override fun provide(targetFrame: MutableAudioFrame, timeout: Long, unit: TimeUnit): Boolean { var track: InternalAudioTrack? = null lastRequestTime = System.currentTimeMillis() - if (timeout == 0L && paused.get()) { + if (timeout == 0L && paused.value) { return false } while (activeTrack.also { track = it } != null) { @@ -245,28 +245,28 @@ class DefaultAudioPlayer(private val manager: DefaultAudioPlayerManager) : Audio } override var volume: Int - get() = options.volumeLevel.get() + get() = options.volumeLevel.value set(volume) { - options.volumeLevel.set(min(1000, max(0, volume))) + options.volumeLevel.value = min(1000, max(0, volume)) } override var filterFactory: PcmFilterFactory? - get() = options.filterFactory.get() + get() = options.filterFactory.value set(factory) { - options.filterFactory.set(factory) + options.filterFactory.value = factory } override var frameBufferDuration: Int - get() = options.frameBufferDuration.get() + get() = options.frameBufferDuration.value set(value) { - options.frameBufferDuration.set(max(200, value)) + options.frameBufferDuration.value = max(200, value) } /** * @return Whether the player is paused */ override var isPaused: Boolean - get() = paused.get() + get() = paused.value set(value) { if (paused.compareAndSet(!value, value)) { if (value) { diff --git a/lib/src/main/kotlin/be/zvz/klover/player/DefaultAudioPlayerManager.kt b/lib/src/main/kotlin/be/zvz/klover/player/DefaultAudioPlayerManager.kt index 04af218..e2962cf 100644 --- a/lib/src/main/kotlin/be/zvz/klover/player/DefaultAudioPlayerManager.kt +++ b/lib/src/main/kotlin/be/zvz/klover/player/DefaultAudioPlayerManager.kt @@ -16,20 +16,19 @@ import be.zvz.klover.track.InternalAudioTrack import be.zvz.klover.track.TrackStateListener import be.zvz.klover.track.playback.AudioTrackExecutor import be.zvz.klover.track.playback.LocalAudioTrackExecutor +import kotlinx.atomicfu.atomic import org.slf4j.LoggerFactory -import java.util.Optional import java.util.concurrent.Executors import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicLong open class DefaultAudioPlayerManager : AudioPlayerManager { override var trackStuckThreshold = 10000L override var playerCleanupThreshold: Long - get() = cleanupThreshold.get() + get() = cleanupThreshold.value set(value) { - cleanupThreshold.set(value) + cleanupThreshold.value = value } - private val cleanupThreshold = AtomicLong(TimeUnit.MINUTES.toMillis(1)) + private val cleanupThreshold = atomic(TimeUnit.MINUTES.toMillis(1)) val manager = Executors.newScheduledThreadPool( 1, @@ -65,8 +64,10 @@ open class DefaultAudioPlayerManager : AudioPlayerManager { return if (customExecutor != null) { customExecutor } else { - val bufferDuration = - Optional.ofNullable(playerOptions.frameBufferDuration.get()).orElse(frameBufferDuration) + val bufferDuration = when (playerOptions.frameBufferDuration.value) { + -1 -> frameBufferDuration + else -> playerOptions.frameBufferDuration.value + } LocalAudioTrackExecutor(track, configuration, playerOptions, isUsingSeekGhosting, bufferDuration) } } diff --git a/lib/src/main/kotlin/be/zvz/klover/tools/thread/DaemonThreadFactory.kt b/lib/src/main/kotlin/be/zvz/klover/tools/thread/DaemonThreadFactory.kt index c5ab9f7..b83063d 100644 --- a/lib/src/main/kotlin/be/zvz/klover/tools/thread/DaemonThreadFactory.kt +++ b/lib/src/main/kotlin/be/zvz/klover/tools/thread/DaemonThreadFactory.kt @@ -1,8 +1,8 @@ package be.zvz.klover.tools.thread +import kotlinx.atomicfu.atomic import org.slf4j.LoggerFactory import java.util.concurrent.ThreadFactory -import java.util.concurrent.atomic.AtomicInteger /** * Thread factory for daemon threads. @@ -12,7 +12,7 @@ import java.util.concurrent.atomic.AtomicInteger */ class DaemonThreadFactory @JvmOverloads constructor(name: String, exitCallback: Runnable? = null) : ThreadFactory { private val group: ThreadGroup = Thread.currentThread().threadGroup - private val threadNumber = AtomicInteger(1) + private val threadNumber = atomic(1) private val namePrefix: String private val exitCallback: Runnable? @@ -67,6 +67,6 @@ class DaemonThreadFactory @JvmOverloads constructor(name: String, exitCallback: companion object { private val log = LoggerFactory.getLogger(DaemonThreadFactory::class.java) - private val poolNumber = AtomicInteger(1) + private val poolNumber = atomic(1) } } diff --git a/lib/src/main/kotlin/be/zvz/klover/track/BaseAudioTrack.kt b/lib/src/main/kotlin/be/zvz/klover/track/BaseAudioTrack.kt index 35f2e81..1d8f6e3 100644 --- a/lib/src/main/kotlin/be/zvz/klover/track/BaseAudioTrack.kt +++ b/lib/src/main/kotlin/be/zvz/klover/track/BaseAudioTrack.kt @@ -7,10 +7,9 @@ import be.zvz.klover.track.playback.AudioFrame import be.zvz.klover.track.playback.AudioTrackExecutor import be.zvz.klover.track.playback.MutableAudioFrame import be.zvz.klover.track.playback.PrimordialAudioTrackExecutor +import kotlinx.atomicfu.atomic import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicLong import kotlin.concurrent.Volatile /** @@ -20,25 +19,23 @@ import kotlin.concurrent.Volatile */ abstract class BaseAudioTrack(trackInfo: AudioTrackInfo) : InternalAudioTrack { private val initialExecutor: PrimordialAudioTrackExecutor - private val executorAssigned: AtomicBoolean + private val executorAssigned = atomic(false) @Volatile private var activeExecutor: AudioTrackExecutor? override val info: AudioTrackInfo = trackInfo - protected val accurateDuration: AtomicLong + protected val accurateDuration = atomic(0L) @Volatile override var userData: Any? = null init { initialExecutor = PrimordialAudioTrackExecutor(trackInfo) - executorAssigned = AtomicBoolean() activeExecutor = null - accurateDuration = AtomicLong() } override fun assignExecutor(executor: AudioTrackExecutor, applyPrimordialState: Boolean) { - activeExecutor = if (executorAssigned.compareAndSet(false, true)) { + activeExecutor = if (executorAssigned.compareAndSet(expect = false, update = true)) { if (applyPrimordialState) { initialExecutor.applyStateToExecutor(executor) } @@ -93,7 +90,7 @@ abstract class BaseAudioTrack(trackInfo: AudioTrackInfo) : InternalAudioTrack { override val duration: Long get() { - val accurate = accurateDuration.get() + val accurate = accurateDuration.value return if (accurate == 0L) { info.length } else { diff --git a/lib/src/main/kotlin/be/zvz/klover/track/TrackMarkerTracker.kt b/lib/src/main/kotlin/be/zvz/klover/track/TrackMarkerTracker.kt index a7c7890..79266c3 100644 --- a/lib/src/main/kotlin/be/zvz/klover/track/TrackMarkerTracker.kt +++ b/lib/src/main/kotlin/be/zvz/klover/track/TrackMarkerTracker.kt @@ -1,13 +1,13 @@ package be.zvz.klover.track import be.zvz.klover.track.TrackMarkerHandler.MarkerState -import java.util.concurrent.atomic.AtomicReference +import kotlinx.atomicfu.atomic /** * Tracks the state of a track position marker. */ class TrackMarkerTracker { - private val current = AtomicReference() + private val current = atomic(null) /** * Set a new track position marker. @@ -44,7 +44,7 @@ class TrackMarkerTracker { * @param timecode Timecode which was reached by normal playback. */ fun checkPlaybackTimecode(timecode: Long) { - val marker = current.get() + val marker = current.value if (marker != null && timecode >= marker.timecode) { trigger(marker, MarkerState.REACHED) } @@ -55,7 +55,7 @@ class TrackMarkerTracker { * @param timecode Timecode which was reached by seeking. */ fun checkSeekTimecode(timecode: Long) { - val marker = current.get() + val marker = current.value if (marker != null && timecode >= marker.timecode) { trigger(marker, MarkerState.BYPASSED) } diff --git a/lib/src/main/kotlin/be/zvz/klover/track/playback/AllocatingAudioFrameBuffer.kt b/lib/src/main/kotlin/be/zvz/klover/track/playback/AllocatingAudioFrameBuffer.kt index 41e78b2..61cdfa0 100644 --- a/lib/src/main/kotlin/be/zvz/klover/track/playback/AllocatingAudioFrameBuffer.kt +++ b/lib/src/main/kotlin/be/zvz/klover/track/playback/AllocatingAudioFrameBuffer.kt @@ -1,11 +1,11 @@ package be.zvz.klover.track.playback import be.zvz.klover.format.AudioDataFormat +import kotlinx.atomicfu.AtomicBoolean import org.slf4j.LoggerFactory import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException -import java.util.concurrent.atomic.AtomicBoolean import kotlin.concurrent.withLock /** @@ -130,7 +130,7 @@ class AllocatingAudioFrameBuffer(bufferDuration: Int, format: AudioDataFormat, s // still trigger. Guarantees that stopped tracks cannot get stuck in this method. Possible performance improvement: // offer with timeout, check stopping if timed out, then put? var frame = frame - if (stopping != null && stopping.get()) { + if (stopping != null && stopping.value) { throw InterruptedException() } if (!locked) { diff --git a/lib/src/main/kotlin/be/zvz/klover/track/playback/AudioFrameBufferFactory.kt b/lib/src/main/kotlin/be/zvz/klover/track/playback/AudioFrameBufferFactory.kt index 86ace2e..a20f5fb 100644 --- a/lib/src/main/kotlin/be/zvz/klover/track/playback/AudioFrameBufferFactory.kt +++ b/lib/src/main/kotlin/be/zvz/klover/track/playback/AudioFrameBufferFactory.kt @@ -1,7 +1,7 @@ package be.zvz.klover.track.playback import be.zvz.klover.format.AudioDataFormat -import java.util.concurrent.atomic.AtomicBoolean +import kotlinx.atomicfu.AtomicBoolean /** * Factory for audio frame buffers. diff --git a/lib/src/main/kotlin/be/zvz/klover/track/playback/LocalAudioTrackExecutor.kt b/lib/src/main/kotlin/be/zvz/klover/track/playback/LocalAudioTrackExecutor.kt index a969bab..542bdbd 100644 --- a/lib/src/main/kotlin/be/zvz/klover/track/playback/LocalAudioTrackExecutor.kt +++ b/lib/src/main/kotlin/be/zvz/klover/track/playback/LocalAudioTrackExecutor.kt @@ -10,13 +10,11 @@ import be.zvz.klover.track.TrackMarker import be.zvz.klover.track.TrackMarkerHandler.MarkerState import be.zvz.klover.track.TrackMarkerTracker import be.zvz.klover.track.TrackStateListener +import kotlinx.atomicfu.atomic import org.slf4j.LoggerFactory import java.io.InterruptedIOException import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicLong -import java.util.concurrent.atomic.AtomicReference import kotlin.concurrent.Volatile /** @@ -39,11 +37,11 @@ class LocalAudioTrackExecutor( val processingContext: AudioProcessingContext private val useSeekGhosting: Boolean override val audioBuffer: AudioFrameBuffer - private val playingThread = AtomicReference() - private val queuedStop = AtomicBoolean(false) - private val queuedSeek = AtomicLong(-1) - private val lastFrameTimecode = AtomicLong(0) - private val state = AtomicReference(AudioTrackState.INACTIVE) + private val playingThread = atomic(null) + private val queuedStop = atomic(false) + private val queuedSeek = atomic(-1L) + private val lastFrameTimecode = atomic(0L) + private val state = atomic(AudioTrackState.INACTIVE) private val actionSynchronizer = Any() private val markerTracker = TrackMarkerTracker() private var externalSeekPosition: Long = -1 @@ -61,10 +59,10 @@ class LocalAudioTrackExecutor( val stackTrace: Array? get() { - val thread = playingThread.get() + val thread = playingThread.value if (thread != null) { val trace = thread.stackTrace - if (playingThread.get() === thread) { + if (playingThread.value === thread) { return trace } } @@ -72,7 +70,7 @@ class LocalAudioTrackExecutor( } override fun getState(): AudioTrackState { - return state.get() + return state.value } override fun execute(listener: TrackStateListener) { @@ -82,7 +80,7 @@ class LocalAudioTrackExecutor( } if (playingThread.compareAndSet(null, Thread.currentThread())) { log.debug("Starting to play track {} locally with listener {}", audioTrack.info.identifier, listener) - state.set(AudioTrackState.LOADING) + state.value = AudioTrackState.LOADING try { audioTrack.process(this) log.debug("Playing track {} finished or was stopped.", audioTrack.identifier) @@ -108,7 +106,7 @@ class LocalAudioTrackExecutor( interrupt = if (interrupt != null) interrupt else findInterrupt(null) playingThread.compareAndSet(Thread.currentThread(), null) markerTracker.trigger(MarkerState.ENDED) - state.set(AudioTrackState.FINISHED) + state.value = AudioTrackState.FINISHED } if (interrupt != null) { Thread.currentThread().interrupt() @@ -121,7 +119,7 @@ class LocalAudioTrackExecutor( override fun stop() { synchronized(actionSynchronizer) { - val thread = playingThread.get() + val thread = playingThread.value if (thread != null) { log.debug("Requesting stop for track {}", audioTrack.identifier) queuedStop.compareAndSet(false, true) @@ -137,7 +135,7 @@ class LocalAudioTrackExecutor( */ fun checkStopped(): Boolean { if (queuedStop.compareAndSet(true, false)) { - state.set(AudioTrackState.STOPPING) + state.value = AudioTrackState.STOPPING return true } return false @@ -161,7 +159,7 @@ class LocalAudioTrackExecutor( */ fun interrupt(): Boolean { synchronized(actionSynchronizer) { - val thread = playingThread.get() + val thread = playingThread.value if (thread != null) { thread.interrupt() return true @@ -172,8 +170,8 @@ class LocalAudioTrackExecutor( override var position: Long get() { - val seek = queuedSeek.get() - return if (seek != -1L) seek else lastFrameTimecode.get() + val seek = queuedSeek.value + return if (seek != -1L) seek else lastFrameTimecode.value } set(timecode) { var timecode = timecode @@ -184,7 +182,7 @@ class LocalAudioTrackExecutor( if (timecode < 0) { timecode = 0 } - queuedSeek.set(timecode) + queuedSeek.value = timecode if (!useSeekGhosting) { audioBuffer.clear() } @@ -196,7 +194,7 @@ class LocalAudioTrackExecutor( /** * @return True if this track is currently in the middle of a seek. */ - private get() = queuedSeek.get() != -1L || useSeekGhosting && audioBuffer.hasClearOnInsert() + private get() = queuedSeek.value != -1L || useSeekGhosting && audioBuffer.hasClearOnInsert() override fun setMarker(marker: TrackMarker?) { markerTracker[marker] = position @@ -218,7 +216,7 @@ class LocalAudioTrackExecutor( return } while (proceed) { - state.set(AudioTrackState.PLAYING) + state.value = AudioTrackState.PLAYING proceed = false try { // An interrupt may have been placed while we were handling the previous one. @@ -258,7 +256,7 @@ class LocalAudioTrackExecutor( synchronized(actionSynchronizer) { if (interruptibleForSeek) { interruptibleForSeek = false - val thread = playingThread.get() + val thread = playingThread.value if (thread != null) { thread.interrupt() interrupted = true @@ -325,7 +323,7 @@ class LocalAudioTrackExecutor( } var seekPosition: Long synchronized(actionSynchronizer) { - seekPosition = queuedSeek.get() + seekPosition = queuedSeek.value if (seekPosition == -1L) { return SeekResult.NO_SEEK } @@ -354,13 +352,13 @@ class LocalAudioTrackExecutor( } private fun applySeekState(seekPosition: Long) { - state.set(AudioTrackState.SEEKING) + state.value = AudioTrackState.SEEKING if (useSeekGhosting) { audioBuffer.setClearOnInsert() } else { audioBuffer.clear() } - queuedSeek.set(-1) + queuedSeek.value = -1 markerTracker.checkSeekTimecode(seekPosition) } @@ -399,7 +397,7 @@ class LocalAudioTrackExecutor( if (!isPerformingSeek) { markerTracker.checkPlaybackTimecode(frame.timecode) } - lastFrameTimecode.set(frame.timecode) + lastFrameTimecode.value = frame.timecode } }