StreetVoice / HysteriaPlayer

Objective-C audio player, sitting on top of AVPlayer
Other
583 stars 99 forks source link

HysteriaPlayer stops playing in background after some time #126

Open zabolotiny opened 7 years ago

zabolotiny commented 7 years ago

Hi, I have noticed such issue - hysteriaplayer stopps playing in background after some time and doesn't continue. I am working on radio application so playing only one url stream. I have configured remote status logs each 20 seconds. When player stopped getPlayingItemCurrentTime was 5345.68 seconds and getPlayingItemDurationTime was 0.0, getHysteriaPlayerStatus = 0 (HysteriaPlayerStatusPlaying). Seems that buffer become empty and AVplayer can't fill it due to some reason. After some time app was terminated due to the fact that playback is paused.

I am testing on a ipad 2 with ios 9.3.2 installed.

Before i have configured hysteria player i tried a lot of different approaches with AVPlayer but results were the same: After some time in background application stopped playing due to the fact that AVPlayer can't fill buffer.

Any ideas or workarounds regarding this issue?

saiday commented 7 years ago

I don't have such issue.

But one thing seems really weird. If your player stopped, how come you get HysteriaPlayerStatusPlaying status. This status simply indicates self.audioPlayer.rate ==1, means AVPlayer still playing.

If this really happens, this might a issue on AVFoundation supporting streaming source.

zabolotiny commented 7 years ago

@saiday Hi I am still investigating the issue and I have found some other details. Maybe you can help me with it. Right after app stops playing, delegate method is called hysteriaPlayerItemPlaybackStall. Player status still = HysteriaPlayerStatusPlaying. And after 180s background process is terminated because app is not playing any audio. There is no any fixed pattern. App can play audio without any issues during 3 hours or even 43 min and then it stops. Do you have an idea how i should process that scenario?

saiday commented 7 years ago

@sheff1422 May I have a sample URL and the code blocks you did when you received hysteriaPlayerItemPlaybackStall?

zabolotiny commented 7 years ago

Sample url looks like this. And my delegate is placed at view controller. It looks like this http://mp3.nexus.org:8000/radiouniversallife.mp3

- (void)hysteriaPlayerItemPlaybackStall:(AVPlayerItem *)item{
    PFObject *log = [PFObject objectWithClassName:@"RadioLogYura"];
    HysteriaPlayer *hysteriaPlayer = [HysteriaPlayer sharedInstance];
    log[@"Status"] = @"hysteriaPlayerItemPlaybackStall";
    log[@"isPlaying"]  = [NSString stringWithFormat:@"%d",hysteriaPlayer.isPlaying];
    log[@"playerState"]  = [NSString stringWithFormat:@"%ld",(long)[hysteriaPlayer getHysteriaPlayerStatus]];
    log[@"CurrentTime"] = [NSString stringWithFormat:@"%.2f", hysteriaPlayer.getPlayingItemCurrentTime];
    log[@"CurrentDuration"] = [NSString stringWithFormat:@"%.2f", hysteriaPlayer.getPlayingItemDurationTime];
    [log saveInBackground];
}

Instance is created like this:

-(void) viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [HysteriaPlayer sharedInstance].delegate = self;
    [HysteriaPlayer sharedInstance].datasource = self;
    [[HysteriaPlayer sharedInstance] setPlayerRepeatMode:HysteriaPlayerRepeatModeOn];
    [self configurePlayer];
    [self configureTimer];
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    [self becomeFirstResponder];
}

Playback toggle

- (void)togglePlayerState {
    if (self.playerState == RadioPlayerStatePause) {
        self.playerState = RadioPlayerStatePlay;
    } else if (self.playerState == RadioPlayerStatePlay){
        self.playerState = RadioPlayerStatePause;
    }
}

Playback state setter

- (void)setPlayerState:(RadioPlayerState)playerState {
    _playerState = playerState;

    if (playerState == RadioPlayerStatePlay) {
        HysteriaPlayer *hysteriaPlayer = [HysteriaPlayer sharedInstance];
        [hysteriaPlayer play];
        [self.antena startAnimating];
        [self.playButton setImage:[UIImage imageNamed:@"stop_button"] forState:UIControlStateNormal];
    }
    else if(playerState == RadioPlayerStatePause) {
        HysteriaPlayer *hysteriaPlayer = [HysteriaPlayer sharedInstance];
        [hysteriaPlayer pause];
        [self.antena stopAnimating];
        [self.playButton setImage:[UIImage imageNamed:@"play_button"] forState:UIControlStateNormal];
    }
}
zabolotiny commented 7 years ago

I am trying to add such thing to handle stall issue. But i am not sure that it will help

- (void)hysteriaPlayerItemPlaybackStall:(AVPlayerItem *)item{
    PFObject *log = [PFObject objectWithClassName:@"RadioLogYura"];
    HysteriaPlayer *hysteriaPlayer = [HysteriaPlayer sharedInstance];
    log[@"Status"] = @"hysteriaPlayerItemPlaybackStall";
    log[@"isPlaying"]  = [NSString stringWithFormat:@"%d",hysteriaPlayer.isPlaying];
    log[@"playerState"]  = [NSString stringWithFormat:@"%ld",(long)[hysteriaPlayer getHysteriaPlayerStatus]];
    log[@"CurrentTime"] = [NSString stringWithFormat:@"%.2f", hysteriaPlayer.getPlayingItemCurrentTime];
    log[@"CurrentDuration"] = [NSString stringWithFormat:@"%.2f", hysteriaPlayer.getPlayingItemDurationTime];

    [log saveInBackground];
    self.playerState = RadioPlayerStatePause;
    __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        weakSelf.playerState = RadioPlayerStatePlay;
    });

}
zabolotiny commented 7 years ago

Unfortunately my workaround doesn't work. The flow looks like this:

  1. hysteriaPlayerItemPlaybackStall
  2. HysteriaPlayer *hysteriaPlayer = [HysteriaPlayer sharedInstance]; [hysteriaPlayer pause]; getHysteriaPlayerStatus = HysteriaPlayerStatusForcePause
  3. After 1.5 second [hysteriaPlayer play]; status is changed :getHysteriaPlayerStatus = HysteriaPlayerStatusPlaying However as i can see from my log buffer is not filling and app goes to sleep in 180s. Any ideas how can i fix that?
zabolotiny commented 7 years ago

@saiday Hey Saiday, Do you have any ideas how i can fix that issue?

saiday commented 7 years ago

No, I don't have my computer till Nov. 7

I'll reply you soon

sheff1422 notifications@github.com 於 2016年11月5日星期六 寫道:

@saiday https://github.com/saiday Hey Saiday, Do you have any ideas how i can fix that issue?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/StreetVoice/HysteriaPlayer/issues/126#issuecomment-258603033, or mute the thread https://github.com/notifications/unsubscribe-auth/AB0vBwWOyYjsja_wgrqy6F3lvGa3c_gaks5q7FmigaJpZM4KiAfo .

saiday commented 7 years ago

@sheff1422

  1. hysteriaPlayerItemPlaybackStall
  2. HysteriaPlayer *hysteriaPlayer = [HysteriaPlayer sharedInstance]; [hysteriaPlayer pause]; getHysteriaPlayerStatus = HysteriaPlayerStatusForcePause
  3. After 1.5 second [hysteriaPlayer play]; status is changed :getHysteriaPlayerStatus = HysteriaPlayerStatusPlaying However as i can see from my log buffer is not filling and app goes to sleep in 180s. Any ideas how can i fix that?

in step 3, you got HysteriaPlayerStatusPlaying, it simply indicates audioPlayer.rate == 1, here's the code reference: https://github.com/StreetVoice/HysteriaPlayer/blob/master/HysteriaPlayer/HysteriaPlayer.m#L619-L638

  1. did audio player actually play when you got HysteriaPlayerStatusPlaying?
  2. you said log buffer is not filling, what is it? I can't find any relating code snippet from your comments.
  3. have you tried hysteriaPlayerRateChanged:(BOOL)isPlaying delegate? it can helps you monitoring player status.
zabolotiny commented 7 years ago

@saiday Hi

  1. Audio player is playing till the time when delegate (void)hysteriaPlayerItemPlaybackStall:(AVPlayerItem *)item is called. Right after that moment player is not playing and remote log looks like this: [hysteriaPlayer getHysteriaPlayerStatus] = HysteriaPlayerStatusPlaying isPlaying = true getPlayingItemDurationTime is not changing getPlayingItemCurrentTime is not chaging
  2. I have configured log timer which is sending such parameters each 20 sec: getHysteriaPlayerStatus isPlaying getPlayingItemDurationTime getPlayingItemCurrentTime PreloadedTime Also application is sending status notifications when one of delegate methods is called. Due to the fact that getPlayingItemDurationTime&getPlayingItemDurationTime is not changing it looks like buffer is not filling anymore.

3) hysteriaPlayerRateChanged:(BOOL)isPlaying is not called when stall delegate is triggered.

Also i tried to test it with other mp3 streams and situation was the same. After some time (1-3 hours of playback) it stopped playing and application goes to sleep due to the fact that player is not playing anymore.

saiday commented 7 years ago

Since I cannot reproduce, I can only provide you some clues. (If you can make a sample project contains your issue, it would be perfect)

The weirdest part is that when you received AVPlayerItemPlaybackStalledNotification sent by AVPlayer instance, your AVPlayer instance's rate still remains 1, this make no sense and I have no such experience. (I never play streaming file, this issue might relating to streaming source type)

Due to the fact that getPlayingItemDurationTime&getPlayingItemDurationTime is not changing it looks like buffer is not filling anymore.

You can confirm "your buffer been filled or not" by set a breakpoint or add some logs on AVPlayerItem's loadedTimeRanges KVO callback, here's a quick code base ref: https://github.com/StreetVoice/HysteriaPlayer/blob/master/HysteriaPlayer/HysteriaPlayer.m#L758

If you receive AVPlayerItem's loadedTimeRanges KVO callback after your player is stalled, then your buffer is filling properly.

zabolotiny commented 7 years ago

@saiday Thanks for your feedback. I tried to monitor buffer with help of delegate - (void)hysteriaPlayerCurrentItemPreloaded:(CMTime)time And as a result it is not fired after stalled notifications is received.

Would you be so kind to say what are you usually do when - (void)hysteriaPlayerItemPlaybackStall:(AVPlayerItem *)item is called? Should i start playing from scratch?

savasadar commented 7 years ago

Hi, Issue still exist. I user Hysteria Player in My Project and Apple reject it! Reason: "Specifically, the application plays silent audio to stay in the background."

So Weird, I check my app in debug mode, It's status really "playing" after music end.

zabolotiny commented 7 years ago

Hi, I have found a couple of interesting things here. Isssue occurs predominantly for ios 9 (especially 9.3.5) When stalled delegate is called you need to stopp and then play again. that helped me to solve the issue.

saiday commented 7 years ago

@sheff1422 Sorry to reply you so late. In case you're still interesting, I've done nothing on - (void)hysteriaPlayerItemPlaybackStall:(AVPlayerItem *)item called.

saiday commented 7 years ago

@savasadar I suggest you resubmit your app or argue with them, HysteriaPlayer did play 0.1 sec sound on very first play request, this is because if your remote audio streaming have a high latency, in some cases app going to background and you are about to play, AudioSession will not initial hence we can not make any sound out.

This is really a big difference from "Specifically, the application plays silent audio to stay in the background."

And one following question:

So Weird, I check my app in debug mode, It's status really "playing" after music end.

which status you mean? HysteriaPlayer or OS?

savasadar commented 7 years ago

@saiday Thanks for your reply. I will resubmit my app because I can't send message to reviewer from resolution center, message form is closed now.

[HysteriaPlayer sharedInstance].isPlaying status still coming "YES".

I fixed it follows:

-(void)hysteriaPlayerDidReachEnd{ [[HysteriaPlayer sharedInstance] pause]; }

And I added my code this. Will this make a problem? [[HysteriaPlayer sharedInstance] setSkipEmptySoundPlaying:YES];

saiday commented 7 years ago

@savasadar No, it's fine! Good luck~

savasadar commented 7 years ago

@saiday I am still trying to get it accepted.

From Apple

  1. 10.0 Before You Submit: Program License Agreement (iOS) PLA 3.3.1

Your app uses public APIs in an unapproved manner, which does not comply with section 3.3.1 of the Apple Developer Program License Agreement.

Specifically, the app plays silent audio to stay in the background.

Since there is no accurate way of predicting how an API may be modified and what effects those modifications may have, Apple does not permit unapproved uses of public APIs in App Store apps.

Next Steps

Please revise your app to ensure that documented APIs are used in the manner prescribed by Apple.

If there are no alternatives for providing the functionality your app requires, we encourage you to file request enhancements to APIs and developer tools.

Alternatively, you may wish to consult with Apple Developer Technical Support to explore alternative solutions.