Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android runAsForeground issue #66

Open
podbasico opened this issue Feb 28, 2023 · 13 comments
Open

Android runAsForeground issue #66

podbasico opened this issue Feb 28, 2023 · 13 comments

Comments

@podbasico
Copy link

Is anyone getting this error below? I have seen lots of it in my logs.

Screenshot 2023-02-27 at 11 14 11 PM

cc: @emilsaj

@emilsaj
Copy link
Contributor

emilsaj commented Feb 28, 2023

Which calls are you making while your app is in the background?

@podbasico
Copy link
Author

Here are a few of the code that could be causing this issue:

I run this code after an item in the playlist finishes (event 50 - playback completed)

onPlaybackCompleted ({ episodeId } = {}) {
    this.markEpisodeAsListened({ episodeId })
    this.playlist = this.playlist.filter(ep => ep.id !== episodeId)
    if (this.playlist.length > 0) {
      this.playNextTrack()
      this.play()
    }
    this.removeItem({ trackId: `${episodeId}` })
}

Here I check if the playlist is actually done:

/**
 * 105: on event playlist completed
 */
case 105: {
  console.log('on event playlist completed - msgType, value -> ', msgType, value);
  const _hasPlayedAllEpisodes = this.hasPlayedAllEpisodes()
  if (!_hasPlayedAllEpisodes) {
    this.playNextTrack()
  }

  if (_hasPlayedAllEpisodes) {
    this.onPlaylistCleared()
  }
  break;
}

Do you think these could possibly be causing the issue?

@Roshankd1
Copy link

Guys, how did you manage to solve issue with build? I am still not able to successfully build android.

Error I am getting is related to the plugin dependencies:

FAILURE: Build completed with 8 failures.

1: Task failed with an exception.

  • What went wrong:
    Execution failed for task ':app:checkDebugAarMetadata'.

Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
Could not find com.devbrackets.android:exomedia:4.3.0.
Searched in the following locations:
- https://dl.google.com/dl/android/maven2/com/devbrackets/android/exomedia/4.3.0/exomedia-4.3.0.pom
- https://repo.maven.apache.org/maven2/com/devbrackets/android/exomedia/4.3.0/exomedia-4.3.0.pom
- file:/path//android/capacitor-cordova-android-plugins/src/main/libs/exomedia-4.3.0.jar
- file:/path//android/capacitor-cordova-android-plugins/src/main/libs/exomedia.jar
- file:/path//android/app/libs/exomedia-4.3.0.jar
- file:/path//android/app/libs/exomedia.jar
Required by:
project :app > project :capacitor-plugin-playlist
Could not find com.google.android.exoplayer:extension-okhttp:2.9.6.
Searched in the following locations:
- https://dl.google.com/dl/android/maven2/com/google/android/exoplayer/extension-okhttp/2.9.6/extension-okhttp-2.9.6.pom
- https://repo.maven.apache.org/maven2/com/google/android/exoplayer/extension-okhttp/2.9.6/extension-okhttp-2.9.6.pom
- file:/path//android/capacitor-cordova-android-plugins/src/main/libs/extension-okhttp-2.9.6.jar
- file:/path//android/capacitor-cordova-android-plugins/src/main/libs/extension-okhttp.jar
- file:/path//android/app/libs/extension-okhttp-2.9.6.jar
- file:/path//android/app/libs/extension-okhttp.jar
Required by:
project :app > project :capacitor-plugin-playlist
Could not find com.devbrackets.android:playlistcore:2.0.1.
Searched in the following locations:
- https://dl.google.com/dl/android/maven2/com/devbrackets/android/playlistcore/2.0.1/playlistcore-2.0.1.pom
- https://repo.maven.apache.org/maven2/com/devbrackets/android/playlistcore/2.0.1/playlistcore-2.0.1.pom
- file:/path//android/capacitor-cordova-android-plugins/src/main/libs/playlistcore-2.0.1.jar
- file:/path//android/capacitor-cordova-android-plugins/src/main/libs/playlistcore.jar
- file:/path//android/app/libs/playlistcore-2.0.1.jar
- file:/path//android/app/libs/playlistcore.jar
Required by:
project :app > project :capacitor-plugin-playlist

  • Try:

Run with --stacktrace option to get the stack trace.
Run with --info or --debug option to get more log output.
Run with --scan to get full insights.
==============================================================================

@emilsaj
Copy link
Contributor

emilsaj commented Apr 13, 2023

Here are a few of the code that could be causing this issue:

I run this code after an item in the playlist finishes (event 50 - playback completed)

onPlaybackCompleted ({ episodeId } = {}) {
    this.markEpisodeAsListened({ episodeId })
    this.playlist = this.playlist.filter(ep => ep.id !== episodeId)
    if (this.playlist.length > 0) {
      this.playNextTrack()
      this.play()
    }
    this.removeItem({ trackId: `${episodeId}` })
}

Here I check if the playlist is actually done:

/**
 * 105: on event playlist completed
 */
case 105: {
  console.log('on event playlist completed - msgType, value -> ', msgType, value);
  const _hasPlayedAllEpisodes = this.hasPlayedAllEpisodes()
  if (!_hasPlayedAllEpisodes) {
    this.playNextTrack()
  }

  if (_hasPlayedAllEpisodes) {
    this.onPlaylistCleared()
  }
  break;
}

Do you think these could possibly be causing the issue?

Sorry for the late reply @podbasico. Did you figure this out?

Is there a reason why you have to manually call play()?
I have solved this issue by just putting all of my audio tracks into the playlist 1 time, and then letting the plugin/native functionality do it's work. If I try to 'manually' modify the audio playback by inserting new items and calling play() without user interaction, I got the same error as you.

@emilsaj
Copy link
Contributor

emilsaj commented Apr 13, 2023

Guys, how did you manage to solve issue with build? I am still not able to successfully build android.

Error I am getting is related to the plugin dependencies:

FAILURE: Build completed with 8 failures.

1: Task failed with an exception.

  • What went wrong:
    Execution failed for task ':app:checkDebugAarMetadata'.

Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
Could not find com.devbrackets.android:exomedia:4.3.0.
Searched in the following locations:

  • Try:

Run with --stacktrace option to get the stack trace.
Run with --info or --debug option to get more log output.
Run with --scan to get full insights.

@Roshankd1 Do you have jcenter() as a repository under you project level build.gradle? I vaguely remember having this issue.

image

As you can see the resource says it's located at jcenter() for version 4.3.0 of Exomedia https://mvnrepository.com/artifact/com.devbrackets.android/exomedia/4.3.0

@Roshankd1
Copy link

@emilsaj You saved my day! Thank you so so much :)

@Roshankd1
Copy link

@emilsaj quick question! is there a way to delay 4 secs after playing every audio file? thanks a ton.

@emilsaj
Copy link
Contributor

emilsaj commented Apr 13, 2023

@Roshankd1 Not sure that's gonna work on Android when playing in the background - I would not think so. Even iOS might not work if audio is suspended and then started without user interaction, but I am not sure.

@Roshankd1
Copy link

Yes I have been trying to listen to complete event - 50 and set timeout, so i can loop within the array of files and play once at a time by invoking playbyTrackId func but something is not working. I will let you know if I make any improvements on this end.

@podbasico
Copy link
Author

Here are a few of the code that could be causing this issue:
I run this code after an item in the playlist finishes (event 50 - playback completed)

onPlaybackCompleted ({ episodeId } = {}) {
    this.markEpisodeAsListened({ episodeId })
    this.playlist = this.playlist.filter(ep => ep.id !== episodeId)
    if (this.playlist.length > 0) {
      this.playNextTrack()
      this.play()
    }
    this.removeItem({ trackId: `${episodeId}` })
}

Here I check if the playlist is actually done:

/**
 * 105: on event playlist completed
 */
case 105: {
  console.log('on event playlist completed - msgType, value -> ', msgType, value);
  const _hasPlayedAllEpisodes = this.hasPlayedAllEpisodes()
  if (!_hasPlayedAllEpisodes) {
    this.playNextTrack()
  }

  if (_hasPlayedAllEpisodes) {
    this.onPlaylistCleared()
  }
  break;
}

Do you think these could possibly be causing the issue?

Sorry for the late reply @podbasico. Did you figure this out?

Is there a reason why you have to manually call play()? I have solved this issue by just putting all of my audio tracks into the playlist 1 time, and then letting the plugin/native functionality do it's work. If I try to 'manually' modify the audio playback by inserting new items and calling play() without user interaction, I got the same error as you.

Thanks for the reply.
After I found out about these errors, I have refactored my whole app and also updated all my dependencies, I was using an old version of capacitor.

But I'm still getting lots of these errors, and not sure how to solve it (this is my first app).
I also wonder if this is normal... do you get these errors in your app?
Screenshot 2023-04-13 at 8 40 58 PM

Here is my player implementation:

export class PlayerService {

  constructor (app) {
    this.app = app
    this.currentPosition = 0
    this.platform = platform
    this.player = new RmxAudioPlayer()
    this.player.initialize()
    this.hasFinishedPlayingBefore = false
    this.shouldSetPlaybackRateFromLocalStorage = false

    // counter is used to send request to update currentTime on server
    this.counter = 0

    this.player.on('status', (data = {}) => {

      const { msgType, trackId, value } = data

      if (msgType && trackId) {
        try {
          this.onStatusChanged({ msgType, trackId, value })
        } catch (error) {
          console.error('error', error)
          throw error
        }
      }

    });
  }

  play () {
    try {
      this.player.play();
    } catch (error) {
      throw error
    }
  }

  pause () {
    try {
      this.player.pause();
    } catch (error) {
      throw error
    }
  }

  async setPlaylist ({ tracks = [], episodeId, currentPosition = 0 } = {}) {
    try {

      const options = {
        playFromPosition: currentPosition,
        startPaused: false,
        retainPosition: true,
        playFromId: `${episodeId}`
      };

      this.player.setPlaylistItems(tracks, options)
      this.currentPosition = currentPosition
      return

    } catch (error) {

      console.error('error', error)
      throw error

    }

  }

  async playTrackById (id, position) {
    this.currentPosition = position
    return await this.player.playTrackById(`${id}`, position)
  }

  setPlaybackRate(playbackRate) {
    if (typeof playbackRate !== 'number') return
    return this.player.setPlaybackRate(playbackRate);
  }

  async setPlayerOptions () {
    const options = {
      verbose: process.env.PROD
    }

    if (platform === 'ios') {
      options.options = { icon: 'icon_bw' }
    }

    if (platform === 'android') {
      options.options = { icon: 'ic_stat_ic_notification' }
    }

    await this.player.setOptions(options);
  }

  seekTo({ position } = {}) {
    const wasPlaying = this.isPlaying;

    this.player.seekTo(position).then(() => {
      this.currentPosition = position

      // this is due to iOS not holding playbackRate
      if (this.platform === 'ios') {
        this.shouldSetPlaybackRateFromLocalStorage = false
      }

      if (wasPlaying) {
        return this.player.play();
      }
    })

  }

  async addItem (trackItem) {
    return await this.player.addItem(trackItem)
  }

  async removeItem (trackItem) {
    return await this.player.removeItem(trackItem)
  }

  async selectTrackById (trackId, currentPosition) {
    return await this.player.selectTrackById(trackId, currentPosition)
  }

  get getCurrentTrackId () {
    return this.player.currentTrack && this.player.currentTrack.trackId
  }

  get currentItem () {
    return this.player.currentTrack
  }

  get isPlaying () {
    return this.player.currentState === 'playing';
  }

  get isPaused () {
    return this.player.currentState === 'paused';
  }

  async onPositionChanged ({ trackId, value }) {
    let { currentPosition } = value

    // Math.trunc returns the integer part of a number by removing a ny fractional digits
    currentPosition = Math.trunc(currentPosition)

    if (currentPosition === 0) return

    this.currentPosition = currentPosition
    this.counter++
    this.app.store.dispatch('episodes/setEpisodeCurrentPosition', { currentPosition, episodeId: Number(trackId) })

    // every x seconds I save the user progress on server
    if (this.counter >= APP_INTERVAL_SEND_UPDATE_POSITION_REQUEST_IN_SECONDS) {
      this.counter = 0
      try {
        this.saveUserCurrentPosition({ episodeId: trackId, currentPosition })
      } catch (error) {
        console.error('error', error)
      }
    }

    // this is to fix iOS not holding playback value
    if (this.platform === 'ios' && this.shouldSetPlaybackRateFromLocalStorage === false && this.isPlaying) {
      this.shouldSetPlaybackRateFromLocalStorage = true
      this.setPlaybackRateFromLocalStorage()
    }
  }

  async setPlaybackRateFromLocalStorage () {
    try {
      // const playbackRate = await this.getPlaybackRateFromLocalStorage()
      const playbackRate = await get(idbKey) || 1
      this.setPlaybackRate(playbackRate)
    } catch (error) {
      // console.log('error', error)
      throw error
    }
  }


  onStatusChanged ({ value, msgType, trackId } = {}) {

    if (!msgType || !trackId) {
      // alert('onStatusChanged error msgType, trackId -> ', msgType, trackId)
      return
    }

    const trackIdNumber = Number(trackId)

    switch (msgType) {

      /**
       * 30: event on playing
       * ios: on
       * web: on
       * android: on
       */
      case 30: {
        // console.log('event on playing - msgType, value -> ', msgType, value);
        if (trackId && trackIdNumber) {
          this.app.store.dispatch('episodes/updateEpisodeToPlaying', { episodeId: trackIdNumber })
        }
        break;
      }

      /**
       * 35: event on paused
       */
      case 35: {
        // console.log('event on paused - msgType, value -> ', msgType, value);
        if (this.platform === 'ios') {
          setTimeout(() => {
            this.shouldSetPlaybackRateFromLocalStorage = false
          }, 300)
        }

        if (this.platform === 'android' && trackId && trackIdNumber) {
          this.app.store.dispatch('episodes/updateEpisodeToPause', { episodeId: trackIdNumber })
        }

        break;
      }

      /**
       * 40: event on playback position changed
       */
      case 40: {
        // console.log('event on playback position changed - msgType, value -> ', msgType, value);
        const positionChangeCallback = ({ value, trackId }) => {
          this.onPositionChanged({ value, trackId })
        }

        if (!throttleFunc) {
          throttleFunc = throttle(positionChangeCallback, 1000)
        }

        throttleFunc({ value, trackId })
        break;
      }

      /**
       * 50: event on playback completed
       */
      case 50: {
        // console.log('event on playback completed - msgType, value -> ', msgType, value);
        this.app.store.dispatch('episodes/updateEpisodeToNotPlaying', { episodeId: trackIdNumber })
        this.app.store.dispatch('episodes/markEpisodeAsListened', { episodeId: trackIdNumber })
        this.app.store.dispatch('episodes/removeEpisodeFromPlaylist', { episodeId: trackIdNumber })
        this.currentPosition = 0

        const hasEpisodeInPlaylist = this.app.store.getters['episodes/hasEpisodeInPlaylist']
        if (!hasEpisodeInPlaylist) {
          const message = 'Lista de reprodução concluída!'
          Notify.create({ message, type: 'positive' })
          this.hasFinishedPlayingBefore = true
        }

        break;
      }

      /**
       * 100: event track changed
       * web: on
       * ios: on
       * android: on
       */
      case 100: {

        if (platform === 'android' || platform === 'ios') {

          const playingEpisode = this.app.store.getters['episodes/getPlaying']
          const episodeId = playingEpisode && playingEpisode.id

          // first click to play an episode these values should be the same
          if (episodeId === trackIdNumber) return

          if (episodeId) {
            this.app.store.dispatch('episodes/updateEpisodeToNotPlaying', { episodeId })
          }

          this.app.store.dispatch('episodes/updateEpisodeToPlaying', { episodeId: trackIdNumber })

        }

        break;
      }

      default: {
      }
    }
  }

  async saveUserCurrentPosition ({ currentPosition, episodeId } = {}) {

    try {

      saveUserPosition({ episodeId: `${episodeId}`, currentPosition })
      this.counter = 0

    } catch (error) {

      console.error('error', error)
      throw error

    }
  }

}

@emilsaj
Copy link
Contributor

emilsaj commented Apr 14, 2023

@podbasico from your code snippet I would say it looks somewhat like my implementation.
Are you calling play, add, remove or select at arbitrary times without user interaction? Especially play, is what I am suspicious about.

@podbasico
Copy link
Author

@podbasico from your code snippet I would say it looks somewhat like my implementation. Are you calling play, add, remove or select at arbitrary times without user interaction? Especially play, is what I am suspicious about.

Nope I'm not, on a previous version of my APP I was doing that, but then realized it could be the reason for the issues.

@Roshankd1
Copy link

@emilsaj @podbasico Guys I am getting an issue with background play even after setting the permission in manifest as instructed in readme.
Basically the only way to resolve it is to give UNRESTRICTED access to the battery usage for app. Any suggestions here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants