so, I am developing a online music player app with json data. here i am using recycelr view to show list of song. I have to face problem in controlling the media player to pause start in another activity. so I create a new file Audio.kt in this file i have create companion object with all the media player methods. And Now I can use mediaplayer anywhere using Audio.[methods]
here is the Audio.kt file
class Audio {
companion object {
val mp = MediaPlayer()
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun loadMediaPlayer(url: String?) {
mp.setAudioAttributes(
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_MEDIA)
.build()
)
mp.setDataSource(url)
mp.prepareAsync()
mp.setOnPreparedListener {
mp.start()
MainActivity.progressBar.visibility = View.GONE
MainActivity.slidingPlayBtn.visibility = View.VISIBLE
MainActivity.slidingPlayBtn.setImageResource(R.drawable.ic_pause)
MainActivity.main_pbar.visibility = View.GONE
MainActivity.mainPlayBtn.visibility = View.VISIBLE
val duration: Long = Audio.mp.duration.toLong()
val time = kotlin.String.format(
"%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(duration),
TimeUnit.MILLISECONDS.toSeconds(duration) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration))
)
MainActivity.totalDur.text = time
MainActivity.seekBar.max = duration.toInt()
}
}
fun startMediaPlayer() {
mp.start()
}
fun pauseMediaPlayer() {
mp.pause()
}
fun stopMediaPlayer() {
if (mp.isPlaying) {
mp.stop()
mp.reset()
}
}
fun isNotPlaying(): Boolean {
return !mp.isPlaying
}
fun mediaCurrPos(): Long {
return mp.currentPosition.toLong()
}
fun mediaPlayerSeek(progress: Int) {
mp.seekTo(progress)
}
fun nextMediaPlayer() {
mp.setNextMediaPlayer(MediaPlayer())
}
}
}
so Now my Question, is this good approach? or I could have to face problems in future
have you any other idea to do this?
Related
I am trying to build an Android app (coded in Kotlin) which plays a random sound file and after it's done, plays another random file and so on. With the current version of the code it seems that more than one files are played at the same time, instead of waiting the previous one to finish.
In the commented lines in my code I tried implementing a countdown timer for each playing of a music file. It results in the following: when I start the app oly one file is played and then it stops (possibly crashes)
class MainActivity : AppCompatActivity() {
fun playSound(running:Int) {
var completition=1
while (running==1 && completition==1) {
completition=0
var rnds = Random.nextInt(0..1)
if (rnds==1 ) {
val mMediaPlayer = MediaPlayer.create(this, R.raw.right)
mMediaPlayer!!.isLooping = false
mMediaPlayer!!.start()
mMediaPlayer!!.setOnCompletionListener { completition=1 }
}
if (rnds==0 ) {val mMediaPlayer = MediaPlayer.create(this, R.raw.left)
mMediaPlayer!!.isLooping = false
mMediaPlayer!!.start()
mMediaPlayer!!.setOnCompletionListener { completition=1 }
}
}
}
fun music(){
var i=1
while(i==1) {
i=0
val random=(0..1).random()
if (random==1){
val refMediaPlayer=MediaPlayer.create(this,R.raw.right)
refMediaPlayer!!.isLooping = false
refMediaPlayer!!.start()
//object :CountDownTimer(1000,100) {
//override fun onTick(p0: Long) {
// TODO("Not yet implemented")
// }
//override fun onFinish() {
//i=1
//}
// }.start();
}
if (random==0){
val refMediaPlayer=MediaPlayer.create(this,R.raw.left)
refMediaPlayer!!.isLooping = false
refMediaPlayer!!.start()
// object :CountDownTimer(1000,100) {
//override fun onTick(p0: Long) {
// TODO("Not yet implemented")
// }
// override fun onFinish() {
// i=1
// }
//}.start();
}
}
}
// 4. Destroys the MediaPlayer instance when the app is closed
// override fun onStop() {
// super.onStop()
// if (mMediaPlayer != null) {
// mMediaPlayer!!.release()
// mMediaPlayer = null
// }
//}
override fun onCreate(savedInstanceState: Bundle?) {
val running=1
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
music()
}
}
I had a similar problem in an application I built. I ended up putting all the sound stuff on a separate thread and pausing the thread for the duration of each audio file. So something like:
// off the main thread
val refMediaPlayer=MediaPlayer.create(this,R.raw.right)
refMediaPlayer!!.isLooping = false
refMediaPlayer!!.start()
// put the exact length of your sound file here in milliseconds
// 5000 = 5 seconds
Thread.sleep(5000L)
The sound will keep playing, but the code's execution will be paused for the amount of time you specify.
I'm not sure of the exact syntax for Kotlin because I use Java.
I am using USB to communicate audio and video from some external hardware.
Video is displayed on a Surface as long as the app is in foreground but audio should keep playing even when in background.
Using threads doesn't seem to work, when the app goes to background the audio starts to stutter and sound very bad. I believe i need to use a service, am i wrong?
But from all the online documentation i can't figure out, what is the best type of service (or any) to use for such long running application and the best way to communicate the large amount of data between its Activity & Surface?
I looked into plain services, WorkManagers, ForegroundService, ServiceSocket and many others
Here is the current code i am using to run the streamed audio from the USB. It is using a coroutine and as said, audio starts to stutter when app in background
class AudioTrackPlayer {
private val channel = Channel<Boolean>(capacity = 15)
private var dataList = mutableListOf<ByteArray>()
private var readIndex = 0
private var writeIndex = 0
private val scope = CoroutineScope(Job() + Dispatchers.IO)
private var audio: AudioTrack? = null
var lastSize = 0
var isPlaying = false
companion object {
private const val DATA_LIST_SIZE = 11
}
init {
scope.launch {
startThread()
}
}
fun config(channels: Int, sampleRate: Int):Boolean {
//......
}
private suspend fun startThread() = withContext(Dispatchers.IO) {
var data:ByteArray
var isPlayingLast = false
var finalByteArray:ByteArray
channel.consumeEach {
if (audio != null) {
if (isPlaying) {
if (readIndex != writeIndex) {
isPlayingLast = true
data = dataList[readIndex]
lastSize = data.size - 8
if (audio?.playState != AudioTrack.PLAYSTATE_PLAYING) {
audio?.play()
}
audio?.write(data, 8, lastSize)
readIndex++
if (readIndex == DATA_LIST_SIZE) {
readIndex = 0
}
}
} else {
if (isPlayingLast) {
isPlayingLast = false
finalByteArray = ByteArray(lastSize * 2)
if (audio?.playState != AudioTrack.PLAYSTATE_PLAYING) {
audio!!.play()
}
audio!!.write(finalByteArray, 0, finalByteArray.size)
audio!!.stop()
audio!!.flush()
writeIndex = readIndex
}
}
}
}
}
fun write(data: ByteArray) {
scope.launch {
if (isPlaying) {
dataList.add(writeIndex, data)
writeIndex++
if (writeIndex == DATA_LIST_SIZE) {
writeIndex = 0
}
channel.send(true)
}
}
}
fun play() {
//...
}
fun stop() {
//....
}
}
here is two urls. First is "https://28api.haii.io/media/video/video_60_1.mp4",and Second is "https://28api.haii.io/media/video/video_70_1.mp4". When I load first video url, it load and play well. But when I load second url, the second video is broken. I can here only audio. So I tried comparing this two video by download.. but I can't understand the difference between this two url. Both are mp4 encoded video. But Exoplayer only load well at first but not at second.... how can I solve this... help me...
private fun initializePlayer() {
if (player == null) {
val trackSelector = DefaultTrackSelector()
trackSelector.setParameters(
trackSelector.buildUponParameters().setMaxVideoSizeSd()
)
player = ExoPlayerFactory.newSimpleInstance(context,trackSelector)
binding.videoPlayer.player = player
binding.videoPlayer.useController=false
binding.playerControl.player = binding.videoPlayer.player
mediaSource = mediaSourceFactory.createMediaSource(Uri.parse(mediaList[currentIndex].url))
player!!.prepare(mediaSource,false,false)
player!!.seekTo(currentWindow, playbackPosition)
player!!.playWhenReady = playWhenReady
setAudioFocus()
player!!.addListener(object : Player.EventListener {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
when (playbackState) {
Player.STATE_IDLE -> {
}
Player.STATE_BUFFERING -> {
}
Player.STATE_READY -> {
}
Player.STATE_ENDED -> {
showPlayButton()
player!!.seekTo(currentWindow, 0)
showThumbnailImage()
player!!.playWhenReady = false
}
else -> {
}
}
}
})
}
}
private fun getVideoSource(url :String){
videoUrl = url
var mediaSource = mediaSourceFactory.createMediaSource(Uri.parse(url))
player!!.prepare(mediaSource)
playbackPosition=0
player!!.seekTo(currentWindow, playbackPosition)
player!!.playWhenReady = false
}
I am using exoplayer in my application. I am using it to play video from a url. What i am trying to do is that i have three different urls for high,medium and low quality of same the video, and i would like to let the user to be able to change the video quality manually.
{
"lowQualityUrl":"string url",
"mediumQualityUrl":"string url",
"highQualityUrl":"string url"
}
In JWplayer there is an option to add different sources/url for different qualities. Is there something similar that can be done in exoplayer...?
Edit : I don't want to play videos one after another. I just want to switch to a different quality of the same video, like in youtube. But instead of using a single url for the source, what i have are 3 different urls for 3 qualities(low,medium,high) of the same video.
I found a solution or rather a workaround for the issue. I am using the exoplayer inside a recylcerview. This code might need some optimization.
I got this idea from another answer which was under this question,which had this github link, but i think the author deleted it.
What i did was create a class for keeping all the urls for a particular video. Then showing a Spinner above the exoplayer, and when user selects a particular quality then i prepare the exoplayer with the new URL, and then seekto to the previously playing position. You can ignore the StringUtils methods.
VideoPlayerConfig.kt
object VideoPlayerConfig {
//Minimum Video you want to buffer while Playing
val MIN_BUFFER_DURATION = 3000
//Max Video you want to buffer during PlayBack
val MAX_BUFFER_DURATION = 5000
//Min Video you want to buffer before start Playing it
val MIN_PLAYBACK_START_BUFFER = 1500
//Min video You want to buffer when user resumes video
val MIN_PLAYBACK_RESUME_BUFFER = 5000
}
VideoQuality.kt
You can change this class according to your need. I need to store exactly 3 urls for low,medium and high quality urls. And i needed to show them in that order as well in the spinner.
class VideoQuality {
private val videoQualityUrls = HashMap<String, String>()
companion object {
val LOW = getStringResource(R.string.low)
val MEDIUM = getStringResource(R.string.medium)
val HIGH = getStringResource(R.string.high)
}
val qualityArray
get() = arrayListOf<String>().apply {
if (hasQuality(LOW)) add(LOW)
if (hasQuality(MEDIUM)) add(MEDIUM)
if (hasQuality(HIGH)) add(HIGH)
}
var defaultVideoQuality: String? = HIGH
var lowQuality: String?
set(value) {
setVideoQualityUrl(LOW, value)
}
get() = videoQualityUrls[LOW] ?: ""
var mediumQuality: String?
set(value) {
setVideoQualityUrl(MEDIUM, value)
}
get() = videoQualityUrls[MEDIUM] ?: ""
var highQuality: String?
set(value) {
setVideoQualityUrl(HIGH, value)
}
get() = videoQualityUrls[HIGH] ?: ""
private fun setVideoQualityUrl(quality: String?, url: String?) {
if (url != null && quality != null) {
videoQualityUrls[quality] = url
}
}
private fun hasQuality(quality: String?): Boolean {
if (videoQualityUrls[quality] == null) {
return false
}
return true
}
fun getVideoQualityUrl(quality: String?): String? {
return videoQualityUrls[quality]
}
}
Methods to implement for the exoplayer
private fun initializePlayer() {
if (exoPlayer == null) {
val loadControl = DefaultLoadControl.Builder()
.setBufferDurationsMs(2 * VideoPlayerConfig.MIN_BUFFER_DURATION, 2 * VideoPlayerConfig.MAX_BUFFER_DURATION, VideoPlayerConfig.MIN_PLAYBACK_START_BUFFER, VideoPlayerConfig.MIN_PLAYBACK_RESUME_BUFFER)
.createDefaultLoadControl()
//Create a default TrackSelector
val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory()
val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
exoPlayer = ExoPlayerFactory.newSimpleInstance(itemView.context, DefaultRenderersFactory(itemView.context), trackSelector, loadControl)
exoPlayer!!.addListener(PlayEventListener())
val videoQualityInfo:VideoQuality = videoListVideoDataHolderData!!.videoQualityUrls //Just an object that i created and stored in a dataHolder for this view.
val url = videoQualityInfo.getVideoQualityUrl(videoQualityInfo.defaultVideoQuality) ?: ""
preparePlayer(url)
}
}
private fun preparePlayer(url: String) {
if (url.isNotEmpty()) {
val mediaSource = buildMediaSource(StringUtils.makeHttpUrl(url))
exoPlayer?.prepare(mediaSource)
videoView.player = exoPlayer
} else {
Log.d(APPTAG, "NO DEFAULT URL")
}
}
private fun buildMediaSource(url: String): ProgressiveMediaSource {
val mUri: Uri = Uri.parse(url)
val dataSourceFactory = DefaultDataSourceFactory(
itemView.context,
Util.getUserAgent(itemView.context, getStringResource(R.string.app_name))
)
val videoSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(mUri)
return videoSource
}
And then in the Spinner/QualitySelector's OnItemSelectedListener
videoQualitySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val currentTime = exoPlayer?.currentPosition
val isReadyToPlay = exoPlayer?.playWhenReady
val urlToBuild = when (videoQualityUrls.qualityArray[position]) {
VideoQuality.LOW -> videoQualityUrls.lowQuality
VideoQuality.MEDIUM -> videoQualityUrls.mediumQuality
else -> videoQualityUrls.highQuality
}
Log.d(APPTAG, "VIDEO DETAILS :::: ${currentTime} ${isReadyToPlay} ${urlToBuild}")
if (!urlToBuild.isNullOrEmpty()) {
val mediaSource = buildMediaSource(StringUtils.makeHttpUrl(urlToBuild))
exoPlayer?.prepare(mediaSource)
exoPlayer?.playWhenReady = isReadyToPlay ?: false
exoPlayer?.seekTo(currentTime ?: 0)
}
}
}
I have implemented the ExoPlayer in my application using the example from the Codelab : https://codelabs.developers.google.com/codelabs/exoplayer-intro/#3, algo with the example from https://medium.com/google-exoplayer/playing-ads-with-exoplayer-and-ima-868dfd767ea, the only difference is that I use AdsMediaSource instead of the deprecated ImaAdsMediaSource.
My Implementation is this:
class HostVideoFullFragment : Fragment(), AdsMediaSource.MediaSourceFactory {
override fun getSupportedTypes() = intArrayOf(C.TYPE_DASH, C.TYPE_HLS, C.TYPE_OTHER)
override fun createMediaSource(uri: Uri?, handler: Handler?, listener: MediaSourceEventListener?): MediaSource {
#C.ContentType val type = Util.inferContentType(uri)
return when (type) {
C.TYPE_DASH -> {
DashMediaSource.Factory(
DefaultDashChunkSource.Factory(mediaDataSourceFactory),
manifestDataSourceFactory)
.createMediaSource(uri, handler, listener)
}
C.TYPE_HLS -> {
HlsMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(uri, handler, listener)
}
C.TYPE_OTHER -> {
ExtractorMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(uri, handler, listener)
}
else -> throw IllegalStateException("Unsupported type for createMediaSource: $type")
}
}
private var player: SimpleExoPlayer? = null
private lateinit var playerView: SimpleExoPlayerView
private lateinit var binding: FragmentHostVideoFullBinding
private var playbackPosition: Long = 0
private var currentWindow: Int = 0
private var playWhenReady = true
private var inErrorState: Boolean = false
private lateinit var adsLoader: ImaAdsLoader
private lateinit var manifestDataSourceFactory: DataSource.Factory
private lateinit var mediaDataSourceFactory: DataSource.Factory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Initialize the adsLoader
adsLoader = ImaAdsLoader(activity as Context, Uri.parse("https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator="))
manifestDataSourceFactory = DefaultDataSourceFactory(
context, Util.getUserAgent(context, "BUO-APP"))//TODO change the applicationName with the right application name
//
mediaDataSourceFactory = DefaultDataSourceFactory(
context,
Util.getUserAgent(context, "BUO-APP"),//TODO change the applicationName with the right application name
DefaultBandwidthMeter())
}
private fun initializePlayer() {
/*
* Since the player can change from null (when we release resources) to not null we have to check if it's null.
* If it is then reset again
* */
if (player == null) {
//Initialize the Exo Player
player = ExoPlayerFactory.newSimpleInstance(DefaultRenderersFactory(activity as Context),
DefaultTrackSelector())
}
val uri = Uri.parse(videoURl)
val mediaSourceWithAds = buildMediaSourceWithAds(uri)
//Bind the view from the xml to the SimpleExoPlayer instance
playerView.player = player
//Add the listener that listens for errors
player?.addListener(PlayerEventListener())
player?.seekTo(currentWindow, playbackPosition)
player?.prepare(mediaSourceWithAds, true, false)
//In case we could not set the exo player
player?.playWhenReady = playWhenReady
//We got here without an error, therefore set the inErrorState as false
inErrorState = false
//Re update the retry button since, this method could have come from a retry click
updateRetryButton()
}
private inner class PlayerEventListener : Player.DefaultEventListener() {
fun updateResumePosition() {
player?.let {
currentWindow = player!!.currentWindowIndex
playbackPosition = Math.max(0, player!!.contentPosition)
}
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
//The player state has ended
//TODO check if there is going to be a UI change here
// if (playbackState == Player.STATE_ENDED) {
// showControls()
// }
// updateButtonVisibilities()
}
override fun onPositionDiscontinuity(#Player.DiscontinuityReason reason: Int) {
if (inErrorState) {
// This will only occur if the user has performed a seek whilst in the error state. Update
// the resume position so that if the user then retries, playback will resume from the
// position to which they seek.
updateResumePosition()
}
}
override fun onPlayerError(e: ExoPlaybackException?) {
var errorString: String? = null
//Check what was the error so that we can show the user what was the correspond problem
if (e?.type == ExoPlaybackException.TYPE_RENDERER) {
val cause = e.rendererException
if (cause is MediaCodecRenderer.DecoderInitializationException) {
// Special case for decoder initialization failures.
errorString = if (cause.decoderName == null) {
when {
cause.cause is MediaCodecUtil.DecoderQueryException -> getString(R.string.error_querying_decoders)
cause.secureDecoderRequired -> getString(R.string.error_no_secure_decoder,
cause.mimeType)
else -> getString(R.string.error_no_decoder,
cause.mimeType)
}
} else {
getString(R.string.error_instantiating_decoder,
cause.decoderName)
}
}
}
if (errorString != null) {
//Show the toast with the proper error
Toast.makeText(activity as Context, errorString, Toast.LENGTH_LONG).show()
}
inErrorState = true
if (isBehindLiveWindow(e)) {
clearResumePosition()
initializePlayer()
} else {
updateResumePosition()
updateRetryButton()
}
}
}
private fun clearResumePosition() {
//Clear the current resume position, since there was an error
currentWindow = C.INDEX_UNSET
playbackPosition = C.TIME_UNSET
}
/*
* This is for the multi window support
* */
private fun isBehindLiveWindow(e: ExoPlaybackException?): Boolean {
if (e?.type != ExoPlaybackException.TYPE_SOURCE) {
return false
}
var cause: Throwable? = e.sourceException
while (cause != null) {
if (cause is BehindLiveWindowException) {
return true
}
cause = cause.cause
}
return false
}
private fun buildMediaSourceWithAds(uri: Uri): MediaSource {
/*
* This content media source is the video itself without the ads
* */
val contentMediaSource = ExtractorMediaSource.Factory(
DefaultHttpDataSourceFactory("BUO-APP")).createMediaSource(uri) //TODO change the user agent
/*
* The method constructs and returns a ExtractorMediaSource for the given uri.
* We simply use a new DefaultHttpDataSourceFactory which only needs a user agent string.
* By default the factory will use a DefaultExtractorFactory for the media source.
* This supports almost all non-adaptive audio and video formats supported on Android. It will recognize our mp3 file and play it nicely.
* */
return AdsMediaSource(
contentMediaSource,
/* adMediaSourceFactory= */ this,
adsLoader,
playerView.overlayFrameLayout,
/* eventListener= */ null, null)
}
override fun onStart() {
super.onStart()
if (Util.SDK_INT > 23) {
initializePlayer()
}
}
override fun onResume() {
super.onResume()
hideSystemUi()
/*
* Starting with API level 24 Android supports multiple windows.
* As our app can be visible but not active in split window mode, we need to initialize the player in onStart.
* Before API level 24 we wait as long as possible until we grab resources, so we wait until onResume before initializing the player.
* */
if ((Util.SDK_INT <= 23 || player == null)) {
initializePlayer()
}
}
}
The ad never shows and if it shows it shows a rendering error E/ExoPlayerImplInternal: Renderer error. which never allows the video to show. I've run the examples from the IMA ads https://developers.google.com/interactive-media-ads/docs/sdks/android/ example code and it doesn't work neither. Does anyone have implemented the Exo Player succesfully with the latest ExoPlayer library version?
Please Help. Thanks!
When on an emulator, be sure to enable gpu rendering on the virtual device
The problem is that the emulator can not render videos. Therefore it wasn't showing the ads or the video. Run the app on a phone and it will work