I have a Xamarin iOS app that I want to stream videos from an API endpoint that supports HTTP range requests. I've reviewed many similar questions here on SO, but I cannot seem to get the AVPlayer to start playing the video file before it is downloaded fully no matter what I try.
I've tried:
KVO on playbackLikelyToKeepUp, playbackBufferEmpty and playbackBufferEmpty to play the video as soon as it is ReadyToPlay
set AutomaticallyWaitsToMinimizeStalling = false on the AVPlayer
set CanUseNetworkResourcesForLiveStreamingWhilePaused = true, and PreferredForwardBufferDuration = 1 on the AVPlayerItem
called PlayImmediatelyAtRate(1) on the AVPlayer
But still the file is downloaded fully before the video starts to play, which causes a delay for the user.
Is it possible to get AVPlayer to start playing a video file before it has completed downloading it, similar to how the HTML video tag does it?
Here is my current code:
private void SetUpPlayer()
{
if (ViewModel.VideoStreamUrl == null)
{
return;
}
// See https://stackoverflow.com/questions/38865797/how-to-play-video-with-avplayerviewcontroller-avkit-in-xamarin-ios
_aVPlayerItem = new AVPlayerItem(ViewModel.VideoStreamUrl)
{
CanUseNetworkResourcesForLiveStreamingWhilePaused = true,
PreferredForwardBufferDuration = 1,
};
// See https://stackoverflow.com/questions/38867190/how-can-i-check-if-my-avplayer-is-buffering/38867386#38867386
_playbackLikelyToKeepUpObserver?.Dispose();
_playbackLikelyToKeepUpObserver = (NSObject)_aVPlayerItem.AddObserver("playbackLikelyToKeepUp",
NSKeyValueObservingOptions.New,
AVPlayerItem_BufferUpdated);
_playbackBufferEmptyObserver?.Dispose();
_playbackBufferEmptyObserver = (NSObject)_aVPlayerItem.AddObserver("playbackBufferEmpty",
NSKeyValueObservingOptions.New,
AVPlayerItem_BufferUpdated);
_playbackBufferFullObserver?.Dispose();
_playbackBufferFullObserver = (NSObject)_aVPlayerItem.AddObserver("playbackBufferFull",
NSKeyValueObservingOptions.New,
AVPlayerItem_BufferUpdated);
_aVPlayer = new AVPlayer(_aVPlayerItem)
{
AutomaticallyWaitsToMinimizeStalling = false,
};
var playerViewController = new AVPlayerViewController
{
Player = _aVPlayer,
};
AddChildViewController(playerViewController);
View.AddSubview(playerViewController.View);
playerViewController.View.Frame = View.Frame;
playerViewController.ShowsPlaybackControls = true;
_aVPlayer.PlayImmediatelyAtRate(1);
}
private void AVPlayerItem_BufferUpdated(NSObservedChange obj)
{
ReportVideoBuffering();
}
private void ReportVideoBuffering()
{
bool isBufferEmpty = _aVPlayerItem != null && _aVPlayerItem.PlaybackBufferEmpty;
Console.WriteLine($"Buffer empty? {isBufferEmpty}");
Console.WriteLine($"Player status? {_aVPlayer.Status}");
if (_aVPlayer.Status == AVPlayerStatus.ReadyToPlay)
{
Console.WriteLine($"Playing video.");
_aVPlayer.Play();
}
}
The simple answer to the question is no, the AVPlayer doesn't support streaming using http range requests and partial content (206) responses. In our case, we have decided to use Azure Media Services to provide a streaming endpoint which we can then use in the iPad app as well as on the web.
Related
following the instructions from:
https://codingwithjoe.com/playing-audio-from-the-web-and-http/ (using his example and AudioProvider class)
I have been using:
audioplayer: ^0.8.1
audioplayer_web: ^0.7.1
to play audio from https link.
The problem it has some weird inconsistent effect.
- after playing few audio, it keeps on playing the same audio eventhough new url is loaded
- after playing few audio, the sound is weird like some part is cut with other audio.
What is a good audio player that accepts url link for flutter that can produce a consistent result ?
the provided audioplayer from your example works great and has good features. From what you are describing for me it seems like you're not closing the session when you play a sound. It seems like you are stacking the sounds which causes weird sounds.
You have to close the instance then. even though the article is outdated (march 2018) the audioplayer has developed further. check their offical guide here:
https://pub.dev/packages/audioplayer
This is version audioplayer 0.8.1 not 3.0 or something..
Example from docs:
Instantiate an AudioPlayer instance
//...
AudioPlayer audioPlugin = AudioPlayer();
//...
Player controls:
audioPlayer.play(url);
audioPlayer.pause();
audioPlayer.stop();
status and current position:
//...
_positionSubscription = audioPlayer.onAudioPositionChanged.listen(
(p) => setState(() => position = p)
);
_audioPlayerStateSubscription = audioPlayer.onPlayerStateChanged.listen((s) {
if (s == AudioPlayerState.PLAYING) {
setState(() => duration = audioPlayer.duration);
} else if (s == AudioPlayerState.STOPPED) {
onComplete();
setState(() {
position = duration;
});
}
}, onError: (msg) {
setState(() {
playerState = PlayerState.stopped;
duration = new Duration(seconds: 0);
position = new Duration(seconds: 0);
});
});
Like I said most of the audio plugins are running in singleton mode with instances. To provide getting weird effects you have to load the next song in the same instance, don't open another new instance, and you wont get any weird effects.
If you want to switch to a different audio player another great one which I used in an app project is the following:
https://pub.dev/packages/audio_manager#-readme-tab-
Hope it helps.
I am coding a video player with Xamarin, AVPlayer and AvPlayerViewController. My code supports AirPlay and configures AVPlayer according docs but is not working quite well. The following scenario seems to fail:
Play a video, send it to Apple TV.
Exit playback as it goes on the Apple TV
Try to resume playback from the current position. This leads to playback directly starting on the Apple TV but the seek which I do to resume from the last playback position kind of fails. What happens is that AvPlayerViewController UI shows that playback starts from the beginning while the Apple TV is playing back from the resume point. The AvPlayerViewController play/pause button is also wrong most of the time and is not reflecting the right play state.
I do the seek so I resume the video like this: wait ( by KVO ) for the avplayer status to become ready to play, seek and then play the video.
Note ... all this works just fine when player is not in airplay mode. Video resumes properly and UI is looking correct.
Code looks like this:
avItem = new AVPlayerItem(avAsset);
avPlayer = new AVPlayer(avItem)
{
AllowsExternalPlayback = true,
UsesExternalPlaybackWhileExternalScreenIsActive = true
};
avPlayerViewController = new AVPlayerViewController()
{
View =
{
ContentMode = UIViewContentMode.ScaleAspectFill,
AutoresizingMask = UIViewAutoresizing.All,
},
UpdatesNowPlayingInfoCenter = false,
Player = avPlayer
};
initialPlaybackTime = TimeSpan.FromSeconds(bookmarkTime);
AddChildViewController(avPlayerViewController);
View.AddSubview(avPlayerViewController.View);
avPlayerViewController.DidMoveToParentViewController(this);
avItem.AddObserver(this, new NSString("status"), NSKeyValueObservingOptions.Initial, IntPtr.Zero);
public override async void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
{
if (Equals(ofObject, avItem) && keyPath.Equals((NSString)"status"))
{
if (!initialPlaybackTimeSet && (avPlayer.Status == AVPlayerStatus.ReadyToPlay))
{
await avPlayer.SeekAsync(CMTime.FromSeconds(initialPlaybackTime.TotalSeconds, avPlayer.CurrentItem.Asset.Duration.TimeScale));
avPlayer.Play();
initialPlaybackTimeSet = true;
}
}
}
How does one loop a video using AVPlayer under Xamarin iOS? ObjectiveC solution suggests the use of an observable notification. It's not clear how to do this with C# syntax or the Xamarin API of the AVPlayer.
You can see the ObjectiveC question and answer here: Looping a video with AVFoundation AVPlayer?
Sorry, #Pandalink, we should have updated an answer here once we figured it out...
We found something rather quite simple:
NSNotificationCenter.DefaultCenter.AddObserver (AVPlayerItem.DidPlayToEndTimeNotification, (notify) => {
player.Seek(CoreMedia.CMTime.Zero);
notify.Dispose ();
});
This worked for me:
AVAsset videoAsset;
AVPlayerItem videoPlayerItem;
AVPlayer videoPlayer;
AVPlayerLayer videoPlayerLayer;
NSObject videoEndNotificationToken;
public override void ViewDidLoad()
{
videoAsset = AVAsset.FromUrl(NSUrl.FromFilename("video.mp4"));
videoPlayerItem = new AVPlayerItem(videoAsset);
videoPlayer = new AVPlayer(videoPlayerItem);
videoPlayerLayer = AVPlayerLayer.FromPlayer(videoPlayer);
videoPlayerLayer.Frame = View.Frame;
View.Layer.AddSublayer(videoPlayerLayer);
videoPlayer.Play();
// Subscribe to video end notification
videoPlayer.ActionAtItemEnd = AVPlayerActionAtItemEnd.None;
videoEndNotificationToken = NSNotificationCenter.DefaultCenter.AddObserver(AVPlayerItem.DidPlayToEndTimeNotification, VideoDidFinishPlaying, videoPlayerItem);
}
private void VideoDidFinishPlaying(NSNotification obj)
{
Console.WriteLine("Video Finished, will now restart");
videoPlayer.Seek(new CMTime(0, 1));
}
I am working on playing live streaming video using MPmovieplayercontroller. I can also display time of video playing. I am able to play video but when it gets buffer and regain its playing state meanwhile not able to get buffering state and so timer is not getting updated properly rather it jumps with some time.Is there any way to get it proper? Please help me to resolve. Thanks in advance
My code is here:-
NSNotificationCenter.defaultCenter().addObserver(self, selector: "moviePlayBackStateChanged:", name: "MPMoviePlayerLoadStateDidChangeNotification",object: moviePlayer)
if(moviePlayer?.playableDuration > 0)
{
currentTime = moviePlayer!.currentPlaybackTime
}
else
{
currentTime = 0
}
func moviePlayBackStateChanged(notification:NSNotification)
{
if(moviePlayer?.loadState == MPMovieLoadState.Playable)
{
currentTime = moviePlayer!.currentPlaybackTime
println("currentTime\(currentTime)")
lblTime?.text = stringFromTimeInterval(currentTime!)
}else if(moviePlayer?.loadState == MPMovieLoadState.Stalled)
{
lblTime?.text = stringFromTimeInterval(currentTime!)
}
}
I'm currently trying to get an mp4 to play in my app. The MP4 plays fine in vlc (basic check to ensure it's not broken).
The code I'm using looks like this
private void startAnimation()
{
using (var pool = new NSAutoreleasePool())
{
InvokeOnMainThread(() =>
{
player = new MPMoviePlayerController(NSUrl.FromFilename("Graphics/videos/noaudio-data-download.mp4"))
{
AllowsAirPlay = true,
Fullscreen = true,
ScalingMode = MPMovieScalingMode.Fill,
RepeatMode = MPMovieRepeatMode.One,
SourceType = MPMovieSourceType.File,
ShouldAutoplay = true,
ControlStyle = MPMovieControlStyle.Fullscreen,
};
player.View.Frame = View.Bounds;
View.AddSubview(player.View);
View.BringSubviewToFront(player.View);
player.PrepareToPlay();
player.Play();
});
}
}
private void stopAnimation()
{
player.Stop();
player.Dispose();
}
All I get is a black screen and the incredibly unhelpful error
2014-01-05 22:00:44.995 ftrack2ios[85614:80b] _itemFailedToPlayToEnd: {
kind = 1;
new = 2;
old = 0;
}
From what I've read on here and other forums, this error can be down to a pile of different reasons, most of them seem to be down to resizing.
The error occurs on a device as well as on the simulator. I'm not sure if this in iOS 7 issue as it used to work on iOS 6 after some playing around.
I have same problem, and I found that
https://discussions.apple.com/message/23203292#23203292
http://www.techisky.com/how-to/play-mp4-on-ios-7-ipad.html