I'm using AVPlayer (I don't need to, but I wanna stream it and start playing as soon as possible) to play an m4a file (it's an iTunes audio preview). Only I only want it to play a part of that file.
I'm able to set a start time but not an end time.
Using a timer is not working because I'm using URL as a http address. I'm playing as it loads, without downloading the file.
I also saw solutions in Objective-C to use KVO to know when music starts playing but I'm thinking this is not the best approach since I'm using swift and also because of glitches that may occur so the song will not stop at the right moment.
You can add a addBoundaryTimeObserverForTimes to your AVPlayer as follow:
update: Xcode 8.3.2 • Swift 3.1
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player: AVPlayer!
var observer: Any!
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://www.example.com/audio.mp3") else { return }
player = AVPlayer(url: url)
let boundary: TimeInterval = 30
let times = [NSValue(time: CMTimeMake(Int64(boundary), 1))]
observer = player.addBoundaryTimeObserver(forTimes: times, queue: nil) {
[weak self] time in
print("30s reached")
if let observer = self?.observer {
self?.player.removeTimeObserver(observer)
}
self?.player.pause()
}
player.play()
print("started loading")
}
}
Related
I am trying to use AVPlayer to play/cache a remote asset using two tools on Github: CachingPlayerItem with Cache. I found the solution elsewhere(scroll down), which nearly gets me there, My issue now is that I have to tap twice on the remote audio asset (a hyperlink in Firebase) to get it to stream. For some mysterious reason, AVPlayer will not play the remote asset unless it is cached in my case. I am aware that I can directly stream the url using AVPlayerItem(url:) but that is not the solution I am seeking; the sample code for CachingPlayerItem say that should not be necessary.
In my tinkering, I think something is happening with the async operations that are performed when I call playerItem.delegate = self. Maybe I am misunderstanding how this asynchronous delegate operation is working... Any clarity and pointers would be appreciated.
import AVKit
import Cache
class AudioPlayer: AVPlayer, ObservableObject, AVAudioPlayerDelegate {
let diskConfig = DiskConfig(name: "DiskCache")
let memoryConfig = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10)
lazy var storage: Cache.Storage<String, Data>? = {
return try? Cache.Storage(diskConfig: diskConfig, memoryConfig: memoryConfig, transformer: TransformerFactory.forData())
}()
/// Plays audio either from the network if it's not cached or from the cache.
func startPlayback(with url: URL) {
let playerItem: CachingPlayerItem
do {
let result = try storage!.entry(forKey: url.absoluteString)
// The video is cached.
playerItem = CachingPlayerItem(data: result.object, mimeType: "audio/mp4", fileExtension: "m4a")
} catch {
// The video is not cached.
playerItem = CachingPlayerItem(url: url)
}
playerItem.delegate = self // Seems to be the problematic line if the result is not cached.
self.replaceCurrentItem(with: playerItem) // This line is different from what you do. The behaviour doesnt change whether I have AVPlayer as private var.
self.automaticallyWaitsToMinimizeStalling = false
self.play()
}
}
extension AudioPlayer: CachingPlayerItemDelegate {
func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingData data: Data) {
// Video is downloaded. Saving it to the cache asynchronously.
storage?.async.setObject(data, forKey: playerItem.url.absoluteString, completion: { _ in })
print("Caching done!")
}
}
I am building an iOS app that uses the youtube-ios-player-helper library to play YouTube videos. I'm trying to set the playback speed, but nothing happens:
class PlayerViewController: UIViewController {
private let player = YTPlayerView()
override func viewDidLoad() {
super.viewDidLoad()
player.delegate = self
view.addSubview(player)
setupPlayerConstraints()
player.load(withVideoId: "123456")
// Tried here, but nothing change:
print("Available playback rates: \(String(describing: player.availablePlaybackRates()))")
player.setPlaybackRate(2.0)
}
}
extension PlayerViewController: YTPlayerViewDelegate {
func playerViewDidBecomeReady(_ playerView: YTPlayerView) {
player.playVideo()
}
func playerView(_ playerView: YTPlayerView, didChangeTo state: YTPlayerState) {
switch (state) {
case.playing:
// Tried here, but again, nothing change:
print("Available playback rates: \(String(describing: player.availablePlaybackRates()))")
player.setPlaybackRate(2.0)
default:
break;
}
}
}
As shown by the code above, I've tried setting playback speed after loading the video and also when player changes it's state to playing. In none of then the speed was changed.
Also, player.availablePlaybackRates returns nil in both cases (that's strange because when I watch the same video using the YouTube app, I can change the playback speed).
I know that setting the playback speed is a suggestion to the player, but on the official YouTube app, changing the speed works for the same video that I'm trying to watch on my app.
Is there anything that I'm missing here?
I had the same problem. But I solved it like this and it works well. in swift 4
private -> public
private func stringFromEvaluingJavaScript(jsToExecute: String) -> String{
Guard let result = self.webView.stringByEvaluingJavaScript (from jsToExecute) else{
return ""
}
return result
}
directly Execute
playerView.stringFromEvaluingJavaScript(jsToExecute): "player.setPlaybackRate(1.5);")
I noticed a very odd behavior when working with AVPlayer from AVFoundation framework. I'm trying to stream (play) some mp3 radio file from internet.
So I created a new project in Xcode and added the following code to the default ViewController:
import UIKit
import AVFoundation
class ViewController: UIViewController {
let player: AVPlayer = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
let asset = AVAsset(url: URL(string: "https://rfcmedia.streamguys1.com/Newport.mp3")!);
let playerItem = AVPlayerItem(asset: asset);
player.replaceCurrentItem(with: playerItem)
player.play();
}
}
There's nothing particularly interesting going on there. I just create an AVPlayerItem instance with the radio station URL and give it to AVPlayer and then ask the player object to play the song and it works just as expected.
However if I remove the player object instantiation from the class block and put it in the viewDidLoad method the code simply doesn't work (doesn't play anything) yet it doesn't crash or spit out any kinds of errors or warning.
import UIKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let player: AVPlayer = AVPlayer()
let asset = AVAsset(url: URL(string: "https://rfcmedia.streamguys1.com/Newport.mp3")!);
let playerItem = AVPlayerItem(asset: asset);
player.replaceCurrentItem(with: playerItem)
player.play();
}
}
I can't understand this behavior. Why is that happening? Is it limited to AVPlayer or I should watch out for cases like this?
As Andrea Mugnaini explained in the comments to my post, my second example is going to cause an immediate deallocation by the ARC (Automatic Reference Counting) as we don't keep a strong reference to the AVPlayer object.
This is a common pitfall of developers new to Apple's ecosystem.
I have an app that I'm adding sounds to. It has a keypad and when the user taps a button, the number animates to show the user that their press went through.
However, since both are happening on the main thread, adding the following code below, the play() function causes a slight delay in the animation. If the user waits ~2 seconds and taps a keypad number again, they see the delay again. So as long as they're hitting keypad numbers under 2s, they don't see another lag.
I tried wrapping the code below in a DispatchQueue.main.async {} block with no luck.
if let sound = CashSound(rawValue: "buttonPressMelody\(count)"),
let url = Bundle.main.url(forResource: sound.rawValue, withExtension: sound.fileType) {
self.audioPlayer = try? AVAudioPlayer(contentsOf: url)
self.audioPlayer?.prepareToPlay()
self.audioPlayer?.play()
}
How can I play this audio and have the animation run without them interfering and with the audio coinciding with the press?
Thanks
I experienced a rather similar problem in my SwiftUI app, and in my case the solution was in proper resource loading / AVAudioPlayer initialization and preparing.
I use the following function to load audio from disk (inspired by ImageStore class from Apple's Landmarks SwiftUI Tutorial)
final class AudioStore {
typealias Resources = [String:AVAudioPlayer]
fileprivate var audios: Resources = [:]
static var shared = AudioStore()
func audio(with name: String) -> AVAudioPlayer {
let index = guaranteeAudio(for: name)
return audios.values[index]
}
static func loadAudio(with name: String) -> AVAudioPlayer {
guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else { fatalError("Couldn't find audio \(name).mp3 in main bundle.") }
do { return try AVAudioPlayer(contentsOf: url) }
catch { fatalError("Couldn't load audio \(name).mp3 from main bundle.") }
}
fileprivate func guaranteeAudio(for name: String) -> Resources.Index {
if let index = audios.index(forKey: name) { return index }
audios[name] = AudioStore.loadAudio(with: name)
return audios.index(forKey: name)!
}
}
In the view's init I initialize the player's instance by calling audio(with:) with proper resource name.
In onAppear() I call prepareToPlay() on view's already initialized player with proper optional unwrapping, and finally
I play audio when the gesture fires.
Also, in my case I needed to delay the actual playback by some 0.3 seconds, and for that I despatched it to the global queue. I should stress that the animation with the audio playback was smooth even without dispatching it to the background queue, so I concluded the key was in proper initialization and preparation. To delay the playback, however, you can only utilize the background thread, otherwise you will get the lag.
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.3) {
///your audio.play() analog here
}
Hope that will help some soul out there.
My code is very simple like this:
import UIKit
import AVFoundation
class VC: UIViewController {
var player:AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("audioName", ofType: "mp3")!)
do{
try player = AVAudioPlayer(contentsOfURL: url)
print("duration", player.duration)// duration 200
player.prepareToPlay()
player.playAtTime(50.0)
}catch{
}
}
}
when player.play() is called, the audio can play normally.
I don't know why the playAtTime function doesn't work.
Please help!
playAtTime plays the sound at a time in the future, specified relative to the device's current time. So the time parameter needs to be the current device time plus your required delay (in seconds):
player.playAtTime(player.currentDeviceTime + 50)
player.currentTime will be 0 when you initialise the player so this isn't the property to use.
Apple doc is here
func playAtTime(time: NSTimeInterval) -> Bool
This function is meant to play the sound some time in the future, based on and greater than deviceCurrentTime, i would suggest to try:
player.playAtTime(player.currentTime + 50.0)