I am a noob in Kotlin.
I've made a streaming music player using Exoplayer2.
For running this music player in background, I tried to bind activity to service.
But it is not working.
I dont know why this player is stopped outside the app even though i add the foreground service.
Can anybody help me? :(
MusicService.kt
var mExoPlayer: SimpleExoPlayer? = null
class MusicService : MediaBrowserServiceCompat() {
private var mMediaSession: MediaSessionCompat? = null
private lateinit var mStateBuilder: PlaybackStateCompat.Builder
private var playbackPosition = 0L
private var currentWindow = 0
private var oldUri: Uri? = null
private val mMediaSessionCallback = object : MediaSessionCompat.Callback() {
override fun onPlayFromUri(uri: Uri?, extras: Bundle?) {
super.onPlayFromUri(uri, extras)
uri?.let {
val mediaSource = extractMediaSourceFromUri(uri)
if (uri != oldUri)
play(mediaSource)
else play()
oldUri = uri
}
}
override fun onPause() {
super.onPause()
pause()
}
override fun onStop() {
super.onStop()
stop()
}
}
override fun onCreate() {
super.onCreate()
initializePlayer()
initializeExtractor()
initializeAttributes()
mMediaSession = MediaSessionCompat(baseContext, "tag for debugging").apply {
setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS )
mStateBuilder = PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PLAY_PAUSE)
setPlaybackState(mStateBuilder.build())
setCallback(mMediaSessionCallback)
setSessionToken(sessionToken)
isActive = true
}
}
private var mAttrs: AudioAttributes? = null
private fun play(mediaSource: MediaSource) {
if (mExoPlayer == null) initializePlayer()
mExoPlayer?.apply {
mAttrs?.let { initializeAttributes() }
mAttrs?.let { setAudioAttributes(it, true) }
prepare(mediaSource)
seekTo(currentWindow, playbackPosition)
playWhenReady = true
}
}
private fun play() {
mExoPlayer?.apply {
mExoPlayer?.playWhenReady = true
updatePlaybackState(PlaybackStateCompat.STATE_PLAYING)
mMediaSession?.isActive = true
}
}
private fun initializePlayer() {
mExoPlayer = ExoPlayerFactory.newSimpleInstance(
this, DefaultRenderersFactory(baseContext)
, DefaultTrackSelector(),
DefaultLoadControl()
)
}
private fun pause() {
mExoPlayer?.apply {
playWhenReady = false
if (playbackState == PlaybackStateCompat.STATE_PLAYING) {
updatePlaybackState(PlaybackStateCompat.STATE_PAUSED)
}
}
}
private fun stop() {
mExoPlayer?.playWhenReady = false
mExoPlayer?.release()
mExoPlayer = null
updatePlaybackState(PlaybackStateCompat.STATE_NONE)
mMediaSession?.isActive = false
mMediaSession?.release()
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
stopSelf()
}
override fun onDestroy() {
super.onDestroy()
stop()
}
private fun updatePlaybackState(state: Int) {
mMediaSession?.setPlaybackState(
PlaybackStateCompat.Builder().setState(
state
, 0L
, 1.0f // Speed playing
).build()
)
}
private fun initializeAttributes() {
mAttrs = AudioAttributes.Builder().setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MUSIC)
.build()
}
private lateinit var mExtractorFactory: ExtractorMediaSource.Factory
private fun initializeExtractor() {
val userAgent = Util.getUserAgent(baseContext, "Application Name")
mExtractorFactory = ExtractorMediaSource.Factory(DefaultDataSourceFactory(this, userAgent))
.setExtractorsFactory(DefaultExtractorsFactory())
}
private fun extractMediaSourceFromUri(uri: Uri): MediaSource {
return mExtractorFactory.createMediaSource(uri)
}
override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
}
override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
return BrowserRoot("", null)
}
playerAct.kt
var audioUrlPass = ""
class playerAct: AppCompatActivity() { private val songUrl: String = audioUrlPass
private lateinit var mMediaBrowserCompat: MediaBrowserCompat
private val connectionCallback: MediaBrowserCompat.ConnectionCallback = object : MediaBrowserCompat.ConnectionCallback() {
override fun onConnected() {
super.onConnected()
mMediaBrowserCompat.sessionToken.also { token ->
val mediaController = MediaControllerCompat(this#playerAct, token)
MediaControllerCompat.setMediaController(this#playerAct, mediaController)
}
playPauseBuild()
}
override fun onConnectionFailed() {
super.onConnectionFailed()
}
}
private val mControllerCallback = object : MediaControllerCompat.Callback() {
}
fun playPauseBuild() {
val mediaController = MediaControllerCompat.getMediaController(this#playerAct)
main_pcv.showTimeoutMs = 0
exo_play.setOnClickListener {
val state = mediaController.playbackState.state
if (state == PlaybackStateCompat.STATE_PAUSED ||
state == PlaybackStateCompat.STATE_STOPPED ||
state == PlaybackStateCompat.STATE_NONE
) {
mediaController.transportControls.playFromUri(Uri.parse(songUrl), null)
}
else if (state == PlaybackStateCompat.STATE_PLAYING ||
state == PlaybackStateCompat.STATE_BUFFERING ||
state == PlaybackStateCompat.STATE_CONNECTING
) {
mediaController.transportControls.pause()
}
}
mediaController.registerCallback(mControllerCallback)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.player_layout)
val componentName = ComponentName(this, MusicService::class.java)
mMediaBrowserCompat = MediaBrowserCompat(
this, componentName, //Identifier for the service
connectionCallback,
null
)
menuButton.setOnClickListener {
super.onBackPressed()
}
btnTime1.setOnClickListener {
val intent = Intent(this, TimerActivity::class.java)
startActivity(intent)
}
playerSeekBar.setOnSeekBarChangeListener(
object : SeekBar.OnSeekBarChangeListener{
override fun onProgressChanged(seekbar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser){
val volumeNum = progress / 100.0f
mExoPlayer!!.setVolume(volumeNum)
}
}
override fun onStartTrackingTouch(seekbar: SeekBar?) { }
override fun onStopTrackingTouch(seekbar: SeekBar?) { }
})
}
override fun onStart() {
super.onStart()controller
Intent(this, MusicService::class.java).also {
mMediaBrowserCompat.connect()
}
}
override fun onStop() {
super.onStop()
val controllerCompat = MediaControllerCompat.getMediaController(this)
controllerCompat?.unregisterCallback(mControllerCallback)
mMediaBrowserCompat.disconnect()
}
}
In order to start the foreground service, you will need a notification shown within 5 seconds of starting it. Check the following guide in the android documentation: https://developer.android.com/guide/topics/media-apps/audio-app/building-a-mediabrowserservice#mediastyle-notifications
Related
In my application, after Splash, Interstitial ads are shown, I do the following on SplashActivity, I load it and after switching to MainActivity I show it, I do everything like this:
SplashActivity
class ActivitySplash : AppCompatActivity(), Ads.AdsCallback {
private val DELAY = 8000L
private var isAdsOpen = false
private lateinit var sBinding: ActivitySplashBinding
private val liveData = MutableLiveData<Boolean>()
override fun adsLoaded() {
super.adsLoaded()
isAdsOpen = true
liveData.postValue(true)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sBinding = ActivitySplashBinding.inflate(layoutInflater)
val view = sBinding.root
setContentView(view)
Ads.preload(this, BuildConfig.ADS_INTERSTITIAL)
Ads.setAdsCallBack(this)
setLiveData()
liveData.observe(this, {
if (it) {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
})
}
private fun setLiveData() {
Handler(Looper.getMainLooper()).postDelayed({
if (!isAdsOpen) {
liveData.postValue(true)
}
}, DELAY)
}
}
MainActivity В onCreate()
Ads.showInter(this, null)
Ads
object Ads {
private const val ALLOWABLE_AMOUNT_REQUEST = 2
private var requestInterAdCount = 0
private var mInterstitialAd: InterstitialAd? = null
private var mAdIsLoading: Boolean = false
private fun loadAd(context: Context, adsId: String) {
val adRequest = AdRequest.Builder().build()
InterstitialAd.load(context, adsId, adRequest, object : InterstitialAdLoadCallback() {
override fun onAdFailedToLoad(onAdFailedToLoad: LoadAdError) {
mInterstitialAd = null
if (requestInterAdCount < ALLOWABLE_AMOUNT_REQUEST) {
requestInterAdCount += 1
loadAd(context, adsId)
}
}
override fun onAdLoaded(interstitialAd: InterstitialAd) {
mInterstitialAd = interstitialAd
adsCallback?.adsLoaded()
mAdIsLoading = true
mInterstitialAd?.fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdDismissedFullScreenContent() {
mInterstitialAd = null
mAdIsLoading = false
}
override fun onAdFailedToShowFullScreenContent(onAdFailedToShowFullScreenContent: AdError?) {
mInterstitialAd = null
mAdIsLoading = false
}
override fun onAdShowedFullScreenContent() {
mAdIsLoading = false
}
}
}
})
}
fun preload(context: Context, adsId: String) {
adsCallback = null
loadAd(context, adsId)
}
interface AdsCallback {
fun adsLoaded() {}
}
private var adsCallback: AdsCallback? = null
fun setAdsCallBack(adsCallback: AdsCallback) {
this.adsCallback = adsCallback
}
fun showInter(activity: Activity?, adsSplashCallback: AdsCallback?) {
this.adsCallback = adsCallback
if (mAdIsLoading && mInterstitialAd != null) {
mInterstitialAd?.show(activity!!)
}
}
}
The problem is in the frequency of impressions, it is almost two times less than in other places of the application, although I use the same code, it seems that in half of the cases it either does not load or does not show ads, but I cannot understand why this is happening. Please tell me what could be wrong, and how to fix it?
I am using exoplayer for playing videos .And for this we are used Fragment instance with pagerstateadapter and viewpager2.
But when scroll fast previous played video sound listen in background as well as in screen video means mix the sound in same video player.
Please help me how to solve this.
1.State adapter
class StoriesPagerAdapter(
fragment: Fragment,
val onClick1: VideoItemAdapter.OnItemClicked?,
val onlikeClick: VideoItemAdapter.OnLikeCLicked?,
val onFollowClick: VideoItemAdapter.OnFollowCLicked?,
val ontrendingClick: VideoItemAdapter.OnTrendCLicked?,
val oniconCLick: VideoItemAdapter.OnIconClick?) : FragmentStateAdapter(fragment) {
val dataList:MutableList<Gettrendingvideos.Data.Postlist>=mutableListOf()
override fun getItemCount(): Int {
return dataList.size
}
fun addAll(movies: MutableList<Gettrendingvideos.Data.Postlist>) {
for (movie in movies) {
add(movie)
}
}
fun add(moive: Gettrendingvideos.Data.Postlist) {
dataList.add(moive)
notifyItemInserted(dataList.size - 1)
}
override fun createFragment(position: Int): Fragment {
return StoryViewFragment.newInstance(
onClick1,
onlikeClick,
onFollowClick,
ontrendingClick,
oniconCLick,
dataList[position]
)
}}
2 Fragment
class StoryViewFragment : Fragment(), CommentFragment.onCommentCountIncrease {
private var storyUrl: String? = null
private var storiesDataModel: Gettrendingvideos.Data.Postlist? = null
lateinit var mView: View
private var simplePlayer: SimpleExoPlayer? = null
private var cacheDataSourceFactory: CacheDataSourceFactory? = null
private val simpleCache = MainApplication.simpleCache
private var toPlayVideoPosition: Int = -1
lateinit var viewModel: MainViewModel
lateinit var preferences: SecurePreferences
private var bool: Boolean? = false
var onItemClick: VideoItemAdapter.OnItemClicked? = null
var onlikeCLicked: VideoItemAdapter.OnLikeCLicked? = null
var onFollowCLicked: VideoItemAdapter.OnFollowCLicked? = null
var onTrendCLicked: VideoItemAdapter.OnTrendCLicked? = null
var onIconClick: VideoItemAdapter.OnIconClick? = null
lateinit var huserId: String
lateinit var token: String
companion object {
fun newInstance(
itemClicked: VideoItemAdapter.OnItemClicked?,
likeCLicked: VideoItemAdapter.OnLikeCLicked?,
onFollowCLicked: VideoItemAdapter.OnFollowCLicked?,
onTrendCLicked: VideoItemAdapter.OnTrendCLicked?,
onIconClick: VideoItemAdapter.OnIconClick?,
storiesDataModel: Gettrendingvideos.Data.Postlist
) = StoryViewFragment()
.apply {
arguments = Bundle().apply {
putParcelable(Constants.KEY_STORY_DATA, storiesDataModel)
}
this.onItemClick = itemClicked
this.onlikeCLicked = likeCLicked
this.onFollowCLicked = onFollowCLicked
this.onTrendCLicked = onTrendCLicked
this.onIconClick = onIconClick
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
mView = inflater.inflate(
R.layout.layout_main,
container,
false
)
return mView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this, ViewModelFactory(RetrofitBuilder.apiService))
.get(MainViewModel::class.java)
preferences =
SecurePreferences(
requireActivity(),
AppConstants.preferenceName,
AppConstants.USER_DETAILS,
true
)
storiesDataModel = arguments?.getParcelable(Constants.KEY_STORY_DATA)
setData()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (!preferences.getString(AppConstants.USER_ID).equals(null)) {
huserId = preferences.getString(AppConstants.USER_ID)!!
Log.d("TAG", "onActivityCreated: $huserId")
}
}
#SuppressLint("SetTextI18n")
private fun setData() {
Log.d("TAG", "setData: $storiesDataModel")
mView.textview2.text = storiesDataModel?.user_name
mView.like_count.text = storiesDataModel?.total_likes.toString()
comment_count.text = storiesDataModel?.total_comments.toString()
mView.textview.text = storiesDataModel?.type
Glide.with(this).load(storiesDataModel?.user_profile_pic).placeholder(R.drawable.profile).into(mView.image)
if (storiesDataModel?.is_like == 0) {
mView.imageView4.setImageResource(R.drawable.ic_like)
} else {
mView.imageView4.setImageResource(R.drawable.ic_like_red)
}
if (storiesDataModel?.is_following!! == 0) {
mView.textview3.text = "Follow"
} else {
mView.textview3.text = "Following"
}
if (storiesDataModel?.user_id.toString()==preferences.getString(AppConstants.USER_ID)) {
mView.textview3.visibility = View.GONE
}
image.setOnClickListener {
if (preferences.getString(AppConstants.token).equals(null)) {
MainActivity().show(childFragmentManager, "")
} else {
preferences.put(
AppConstants.OtherProfile_UserId,
storiesDataModel?.user_id.toString()
)
}
}
val simplePlayer = getPlayer()
player_view_story.player = simplePlayer player_view_story.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL)
simplePlayer?.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)
storyUrl = storiesDataModel?.video_url
prepareMedia(storiesDataModel)
}
override fun onPause() {
pauseVideo()
super.onPause()
}
override fun onResume() {
restartVideo()
super.onResume()
}
override fun onDestroy() {
releasePlayer()
super.onDestroy()
}
private fun pausePlayer() {
simplePlayer?.setPlayWhenReady(false)
simplePlayer?.getPlaybackState()
}
private fun startPlayer() {
simplePlayer?.setPlayWhenReady(true)
simplePlayer?.getPlaybackState()
}
private val playerCallback: Player.EventListener? = object : Player.EventListener {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
}
override fun onPlayerError(error: com.google.android.exoplayer2.ExoPlaybackException?) {
super.onPlayerError(error)
}
}
private fun prepareVideoPlayer() {
simplePlayer = ExoPlayerFactory.newSimpleInstance(context)
cacheDataSourceFactory = CacheDataSourceFactory(
simpleCache,
DefaultHttpDataSourceFactory(
Util.getUserAgent(
context,
"exo"
)
)
)
}
private fun getPlayer(): SimpleExoPlayer? {
if (simplePlayer == null) {
prepareVideoPlayer()
}
return simplePlayer
}
private fun prepareMedia(datamodel: Gettrendingvideos.Data.Postlist?{
val uri = Uri.parse(datamodel?.video_url)
simplePlayer.repeatMode = Player.REPEAT_MODE_ONE
simplePlayer.playWhenReady = true
if (storiesDataModel!!.type == "following") {
following_page.typeface = Typeface.DEFAULT_BOLD
trending_page.setTypeface(null, Typeface.NORMAL)
} else {
following_page.setTypeface(null, Typeface.BOLD)
}
if (storiesDataModel.type == "treading") {
trending_page.typeface = Typeface.DEFAULT_BOLD
following_page.setTypeface(null, Typeface.NORMAL)
} else {
trending_page.setTypeface(null, Typeface.BOLD)
}
if (simplePlayer.playWhenReady == true) {
}
simplePlayer.addListener(playerCallback)
toPlayVideoPosition = -1
}
private fun setArtwork(drawable: Drawable, playerView: PlayerView) {
playerView.useArtwork = true
playerView.defaultArtwork = drawable
}
private fun playVideo() {
simplePlayer.playWhenReady = true
}
private fun restartVideo() {
if (simplePlayer == null) {
prepareMedia(storiesDataModel)
} else {
simplePlayer.seekToDefaultPosition()
simplePlayer.playWhenReady = true
}
}
private fun pauseVideo() {
simplePlayer.playWhenReady = false
}
private fun releasePlayer() {
simplePlayer.stop(true)
simplePlayer.release()
}}
override fun setMenuVisibility(menuVisible: Boolean) {
if (!menuVisible){
simplePlayer?.playWhenReady = false
simplePlayer?.pause()
}
super.setMenuVisibility(menuVisible)
}
JUST ADD THIS IN YOUR StoryViewFragment.
Following is my service class
class LocalAudioService : Service() {
var player: SimpleExoPlayer? = null
private var playerNotificationManager: PlayerNotificationManager? = null
private var mediaSession: MediaSessionCompat? = null
private var mediaSessionConnector: MediaSessionConnector? = null
private var allInOneApplication: AllInOneApplication? = null
private val binder = LocalBinder()
override fun onDestroy() {
mediaSession?.release()
mediaSessionConnector?.setPlayer(null, null)
playerNotificationManager?.setPlayer(null)
player?.release()
player = null
super.onDestroy()
}
override fun onBind(p0: Intent?): IBinder? {
return binder
}
fun getPlayerInstance(): SimpleExoPlayer? {
if (player == null) {
startPlayer()
}
return player
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if(player == null){
startPlayer()
}
return START_STICKY
}
private fun startPlayer(){
val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory()
val trackSelector = DefaultTrackSelector(this, videoTrackSelectionFactory)
allInOneApplication = AllInOneApplication.getInstance()
val renderersFactory = (application as AllInOneApplication).buildRenderersFactory(false)
player =
SimpleExoPlayer.Builder(this, renderersFactory).setTrackSelector(trackSelector).build()
val dataSourceFactory = DefaultDataSourceFactory(
this,
Util.getUserAgent(this, getString(R.string.app_name))
)
// val cacheDataSourceFactory = CacheDataSourceFactory(
// DownloadUtil.getCache(this),
// dataSourceFactory,
// CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR
// )
val concatenatingMediaSource = ConcatenatingMediaSource()
allInOneApplication?.localAudios?.map {
val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(it?.audioUri)
concatenatingMediaSource.addMediaSource(mediaSource)
}
player?.prepare(concatenatingMediaSource)
player?.playWhenReady = true
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
this,
AUDIO_CHANNEL_ID,
R.string.audio_channel_name,
R.string.audio_channel_name,
NOTIFICATION_ID,
object : MediaDescriptionAdapter {
override fun createCurrentContentIntent(player: Player): PendingIntent? {
val intent = Intent(this#LocalAudioService, LocalAudioPlayer::class.java)
return PendingIntent.getActivity(
this#LocalAudioService,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
override fun getCurrentContentText(player: Player): CharSequence? {
return allInOneApplication?.localAudios?.get(player.currentWindowIndex)?.title
}
override fun getCurrentContentTitle(player: Player): CharSequence {
return allInOneApplication?.localAudios?.get(player.currentWindowIndex)?.title.toString()
}
override fun getCurrentLargeIcon(
player: Player,
callback: PlayerNotificationManager.BitmapCallback
): Bitmap? {
return BitmapFactory.decodeResource(resources, R.drawable.audio)
}
}, object : PlayerNotificationManager.NotificationListener {
override fun onNotificationPosted(
notificationId: Int,
notification: Notification,
ongoing: Boolean
) {
startForeground(notificationId, notification)
}
override fun onNotificationCancelled(
notificationId: Int,
dismissedByUser: Boolean
) {
stopSelf()
}
}
)
playerNotificationManager?.setPlayer(player)
mediaSession = MediaSessionCompat(this, MEDIA_SESSION_TAG)
mediaSession?.isActive = true
mediaSession?.sessionToken?.let { playerNotificationManager?.setMediaSessionToken(it) }
mediaSessionConnector = MediaSessionConnector(mediaSession)
mediaSessionConnector?.setQueueNavigator(object : TimelineQueueNavigator(mediaSession) {
override fun getMediaDescription(
player: Player?,
windowIndex: Int
): MediaDescriptionCompat? {
return allInOneApplication?.getMediaDescription(
this#LocalAudioService,
allInOneApplication?.localAudios?.get(windowIndex)
)
}
})
mediaSessionConnector?.setPlayer(player, null)
}
inner class LocalBinder : Binder() {
val service: LocalAudioService
get() = this#LocalAudioService
}
}
Following is my Activity class
class LocalAudioPlayer : AppCompatActivity() {
private var localAudioService: LocalAudioService? = null
private var mBound = false
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
val binder: LocalAudioService.LocalBinder = iBinder as LocalAudioService.LocalBinder
localAudioService = binder.service
mBound = true
initializePlayer()
}
override fun onServiceDisconnected(componentName: ComponentName) {
mBound = false
}
}
private fun initializePlayer() {
if (mBound) {
val player: SimpleExoPlayer? = localAudioService?.getPlayerInstance()
exoplayer_local_audio_player.player = player
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_local_audio_player)
Intent(this,LocalAudioService::class.java).also {
Util.startForegroundService(this,it)
exoplayer_local_audio_player.useController = true
exoplayer_local_audio_player.showController()
exoplayer_local_audio_player.controllerAutoShow = true
exoplayer_local_audio_player.controllerHideOnTouch = false
}
}
override fun onStart() {
super.onStart()
bindService(intent,mConnection, Context.BIND_AUTO_CREATE)
initializePlayer()
//TODO do any UI setup
}
override fun onStop() {
unbindService(mConnection)
mBound = false
super.onStop()
}
}
I tried following this question Binding PlayerView with SimpleExoPlayer from a service but the sole does not works for me
Try this:
override fun onStart() {
super.onStart()
Intent(this, LocalAudioService::class.java).also { intent ->
bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}
}
initializePlayer() should not be called here. At this moment the Service is not bound yet. When the service is bound it will called in the onServiceConnected callback.
bindService(intent,mConnection, Context.BIND_AUTO_CREATE)
intent is null here . Do Asure you are getting correct and non null intent.It should be global declared as in reference example
intent = new Intent(this, AudioPlayerService.class);
here intent is globally declared.
I have a class BatteryInfo which is a life-cycle observer and in this class, there is a BroadCastReceiver which is responsible for getting all battery information. This class is working perfectly with the lifecycle of Activity from where I have called it. This means it is registering the broadcast on activity created and unRegister on closing activity. But I am confused about how to access this broadcast live information in Activity.
class BatteryInfo(
private val _context: Context,
private val _lifecycle: Lifecycle,
): LifecycleObserver {
private var _enabled = false
init {
_lifecycle.addObserver(this)
}
private val broadcastBatteryInfoListener = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
val level = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val temperature = it.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1)
val voltage = it.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1)
val technology = it.getIntExtra(BatteryManager.EXTRA_TECHNOLOGY, -1)
val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
val health = it.getIntExtra(BatteryManager.EXTRA_HEALTH, -1)
// Log.d("TAG", String.format("%.1f", voltage / 1000f) + " V")
}
}
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
// if (_enabled) {
_context.registerReceiver(broadcastBatteryInfoListener,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
// }
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
_context.unregisterReceiver(broadcastBatteryInfoListener)
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun destroy() {
_lifecycle.removeObserver(this)
}
// connect if not connected
fun enable() {
_enabled = true
if (_lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
start()
}
}
}
I am Calling this class from MainActivity onCreate method like this
BatteryInfo(this, lifecycle)
A simple listener would work :
data class Stats(val level : Int,
val temp : Int,
val voltage : Int,
val technology : Int,
val plugged : Int,
val health : Int)
class BatteryInfo(
private val _context: Context,
private val _lifecycle: Lifecycle
): LifecycleObserver {
private var _enabled = false
private var listener: ((Stats) -> Unit)? = null
init {
_lifecycle.addObserver(this)
}
private val broadcastBatteryInfoListener = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
val level = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val temperature = it.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1)
val voltage = it.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1)
val technology = it.getIntExtra(BatteryManager.EXTRA_TECHNOLOGY, -1)
val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
val health = it.getIntExtra(BatteryManager.EXTRA_HEALTH, -1)
listener?.invoke(Stats(level, temperature, voltage, technology, plugged, health))
// Log.d("TAG", String.format("%.1f", voltage / 1000f) + " V")
}
}
}
fun setListener(listener: ((Stats) -> Unit)?) {
this.listener = listener
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
// if (_enabled) {
_context.registerReceiver(broadcastBatteryInfoListener,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
// }
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
_context.unregisterReceiver(broadcastBatteryInfoListener)
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun destroy() {
_lifecycle.removeObserver(this)
listener = null
}
// connect if not connected
fun enable() {
_enabled = true
if (_lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
start()
}
}
}
class SomeActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
BatteryInfo(this, lifecycle)
.apply { setListener { stats: Stats -> Log.d(TAG, stats.toString()) } }
}
}
I know its not you answer but its a different approach of getting things done. It also removes all of your boilerplate code.
You can use an object to extract all your data from intent and return an instance of your data class.
Read more about object in kotlin here - https://kotlinlang.org/docs/tutorials/kotlin-for-py/objects-and-companion-objects.html
object BatteryInfoUtils {
fun getBatteryInfoFromIntent(intent: Intent): Stats { intent?.let {
val level = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val temperature = it.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1)
val voltage = it.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1)
val technology = it.getIntExtra(BatteryManager.EXTRA_TECHNOLOGY, -1)
val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
val health = it.getIntExtra(BatteryManager.EXTRA_HEALTH, -1)
return Stats(level, temperature, voltage, technology, plugged, health)
}
}
And inside MainActivity onCreate()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val stats = getBatteryInfo()
}
fun getBatteryInfo(): Stats? {
var stats: Stats? = null
val intent: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { ifilter ->
registerReceiver(null, ifilter)
}
intent?.let { batteryIntent ->
stats = BatteryInfoUtils.getBatteryInfoFromIntent(batteryIntent)
}
return stats
}
I have done this with the help of MutableLiveData & LiveData but I am not sure it is a perfect solution or a hacky solution. I want to do a perfect solution that follows MVVM, clean-architecture, and SOLID principle without the creation of multiple objects because it is very costly.
class BatteryInfo(
private val _context: Context,
private val _lifecycle: Lifecycle
) : LifecycleObserver {
val map = HashMap<String, Int>()
private var _enabled = false
private val _batteryInfoMap = MutableLiveData<HashMap<String, Int>>()
val batteryInfo: LiveData<HashMap<String, Int>>
get() = _batteryInfoMap
init {
_lifecycle.addObserver(this)
}
private val broadcastBatteryInfoListener = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.let {
val level = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val temperature = it.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1)
val voltage = it.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1)
val technology = it.getIntExtra(BatteryManager.EXTRA_TECHNOLOGY, -1)
val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
val health = it.getIntExtra(BatteryManager.EXTRA_HEALTH, -1)
map["level"] = level
map["temperature"] = temperature
_batteryInfoMap.value = map
}
}
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
if (_enabled) {
_context.registerReceiver(
broadcastBatteryInfoListener,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
}
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
_context.unregisterReceiver(broadcastBatteryInfoListener)
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun destroy() {
_lifecycle.removeObserver(this)
}
fun enable() {
_enabled = true
if (_lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
start()
}
}
}
And inside MainActivity onCreate()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val into = BatteryInfo(this, lifecycle)
into.enable()
into.batteryInfo.observe(this, Observer {
Log.d("TAG", "${it.values}")
})
}
I am trying to play audio using exoplayer.
I have multiple files in a local directory and I want to play them sequentially. I am trying to add multiple to URIs to MediaSource, but I couldn't find the solution.
Here, the first file is playing correctly but none of the rest. I am using the ExoPlayer version 2.9.2
Note: In my case the files in the "Articles" folder will be updated dynamically while player is running.
Please suggest a solution. My code:
class AudioPlayerService : Service() {
private var player: SimpleExoPlayer? = null
private var playerNotificationManager: PlayerNotificationManager? = null
private var mediaSession: MediaSessionCompat? = null
private var mediaSessionConnector: MediaSessionConnector? = null
private var dataSourceFactory: DataSource.Factory? = null
private var extractorsFactory: ExtractorsFactory? = null
private var dynamicConcatenatingMediaSource: ConcatenatingMediaSource? = null
override fun onCreate() {
super.onCreate()
val context: Context = this
player = ExoPlayerFactory.newSimpleInstance(this)
dataSourceFactory = DefaultDataSourceFactory(context, BuildConfig.APPLICATION_ID)
extractorsFactory = DefaultExtractorsFactory()
var file: File = File(Environment.getExternalStorageDirectory().absolutePath + "/Articles")
/*for (i in file.listFiles()){
Log.e("","" +file.listFiles())
}*/
var uri =
Uri.parse(Environment.getExternalStorageDirectory().absolutePath + "/Articles" + "/article_tts" + 0 + ".wav")
//if (dynamicConcatenatingMediaSource == null)
dynamicConcatenatingMediaSource = ConcatenatingMediaSource()
val mediaSource: MediaSource =
ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri)
dynamicConcatenatingMediaSource?.addMediaSource(mediaSource)
player?.prepare(dynamicConcatenatingMediaSource)
player?.setPlayWhenReady(true)
player?.addListener(object : Player.EventListener {
override fun onTracksChanged(
trackGroups: TrackGroupArray?,
trackSelections: TrackSelectionArray?
) {
}
override fun onPlayerError(error: ExoPlaybackException?) {
stopSelf()
}
override fun onLoadingChanged(isLoading: Boolean) {
}
override fun onPositionDiscontinuity(reason: Int) {
}
override fun onRepeatModeChanged(repeatMode: Int) {
}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
}
override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
if (playbackState == Player.STATE_ENDED) {
player?.stop()
stopSelf()
}
}
override fun onSeekProcessed() {
}
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {
}
})
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
context,
"playback_channel",
R.string.playback_channel_name,
7001,
object : MediaDescriptionAdapter {
override fun getCurrentContentTitle(player: Player): String {
return "Playlist"
}
override fun createCurrentContentIntent(player: Player): PendingIntent? {
return null
}
override fun getCurrentContentText(player: Player): String? {
return "Playlist Demo"
}
override fun getCurrentLargeIcon(
player: Player,
callback: BitmapCallback
): Bitmap? {
return getBitmap(context, R.drawable.ic_vikatan_logo_new)
}
}
)
playerNotificationManager?.setNotificationListener(object :
PlayerNotificationManager.NotificationListener {
override fun onNotificationStarted(
notificationId: Int,
notification: Notification
) {
startForeground(notificationId, notification)
}
override fun onNotificationCancelled(notificationId: Int) {
stopSelf()
}
})
playerNotificationManager?.setPriority(NotificationCompat.PRIORITY_HIGH);
playerNotificationManager?.setUseNavigationActions(false)
playerNotificationManager?.setFastForwardIncrementMs(0)
playerNotificationManager?.setRewindIncrementMs(0)
playerNotificationManager?.setUsePlayPauseActions(true);
playerNotificationManager?.setPlayer(player)
mediaSession = MediaSessionCompat(context, "audio")
mediaSession?.setActive(true)
playerNotificationManager?.setMediaSessionToken(mediaSession?.getSessionToken())
mediaSessionConnector = MediaSessionConnector(mediaSession)
mediaSessionConnector?.setQueueNavigator(object : TimelineQueueNavigator(mediaSession) {
override fun getMediaDescription(
player: Player,
windowIndex: Int
): MediaDescriptionCompat {
return getMediaDescription(context)
}
})
mediaSessionConnector?.setPlayer(player, null)
}
override fun onDestroy() {
mediaSession!!.release()
mediaSessionConnector!!.setPlayer(null, null)
playerNotificationManager!!.setPlayer(null)
player!!.release()
player = null
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
fun getBitmap(context: Context, #DrawableRes bitmapResource: Int): Bitmap {
return (context.resources.getDrawable(bitmapResource) as BitmapDrawable).bitmap
}
fun getMediaDescription(context: Context?): MediaDescriptionCompat {
val extras = Bundle()
val bitmap: Bitmap = getBitmap(context!!, R.drawable.ic_vikatan_logo_new)
extras.putParcelable(
MediaMetadataCompat.METADATA_KEY_ALBUM_ART,
bitmap
)
extras.putParcelable(
MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON,
bitmap
)
return MediaDescriptionCompat.Builder()
.setMediaId("audio_id")
.setIconBitmap(bitmap)
.setTitle("Playlist")
.setDescription("Playlist Demo")
.setExtras(extras)
.build()
}
}
Use the concatenating media source.
private val concatenatingMediaSource = ConcatenatingMediaSource()
Then create separate mediasource for each audio file and add to concatenatingMediaSource by using
concatenatingMediaSource.addMediaSource(your_media_source_object)