如何在NativeScript插件中为iOS的AVPlayer添加事件观察者
在NativeScript iOS中用AVPlayer处理流媒体播放事件与变量管理(解决崩溃问题)
我明白你现在的处境——nativescript-audio在Android上跑的好好的,到iOS因为AVAudioPlayer不支持流媒体不得不换成AVPlayer,结果折腾事件监听和变量管理的时候还崩了。别着急,咱们一步步来解决这个问题。
一、AVPlayer播放结束事件的正确监听方式
AVPlayer本身没有直接的“播放结束”回调,得通过它的AVPlayerItem来监听,主要有两种可靠方式:
- 利用通知中心监听
AVPlayerItemDidPlayToEndTimeNotification:这是系统提供的最直接的播放结束通知 - 监听
AVPlayerItem的status属性:用来处理播放出错、缓冲完成等状态变化,避免因状态异常触发崩溃
二、变量与观察者的安全管理(避免崩溃的核心)
崩溃大概率和观察者未正确移除、变量引用不当有关,这里给你几个关键要点:
- 务必在组件销毁时清理所有观察者:NativeScript的页面/组件都有
unloaded或destroyed事件,一定要在这里移除通知监听和KVO观察者,不然会导致内存泄漏甚至野指针崩溃 - 不要直接持有未初始化的实例引用:在创建AVPlayer/AVPlayerItem前要做空值判断,避免空指针调用
- UI操作必须在UI线程执行:如果事件回调里要更新UI(比如播放结束后切换按钮状态),记得用
Application.runOnUIThread()包裹
三、完整代码示例(TypeScript)
import { Application, Page } from "@nativescript/core"; import { ios as iosUtils } from "@nativescript/core"; export class RadioPlayer { private avPlayer: AVPlayer; private playerItem: AVPlayerItem; private endTimeObserver: NSObject; constructor(private page: Page) { // 绑定页面卸载事件,确保资源能被清理 this.page.on("unloaded", () => this.cleanup()); } initPlayer(streamUrl: string) { // 创建AVPlayerItem实例 const url = NSURL.URLWithString(streamUrl); this.playerItem = AVPlayerItem.alloc().initWithURL(url); // 监听播放结束通知 this.endTimeObserver = NSNotificationCenter.defaultCenter.addObserverForNameObjectQueueUsingBlock( AVPlayerItemDidPlayToEndTimeNotification, this.playerItem, null, (notification) => { // 播放结束后的业务逻辑 Application.runOnUIThread(() => { console.log("播放结束"); // 这里可以更新UI,比如切换播放按钮状态 }); } ); // 监听AVPlayerItem的状态变化,处理缓冲、出错等场景 this.playerItem.addObserverForKeyPathOptionsContext( this, "status", NSKeyValueObservingOptions.New, null ); // 初始化AVPlayer并开始播放 this.avPlayer = AVPlayer.alloc().initWithPlayerItem(this.playerItem); this.avPlayer.play(); } // KVO回调处理状态变化 observeValueForKeyPathOfObjectChangeContext(keyPath: string, object: any, change: NSDictionary, context: any) { if (keyPath === "status") { const status = this.playerItem.status; if (status === AVPlayerStatus.Failed) { console.error("播放出错:", this.playerItem.error?.localizedDescription); } else if (status === AVPlayerStatus.ReadyToPlay) { console.log("缓冲完成,开始播放"); } } } // 清理资源,彻底避免崩溃 cleanup() { if (this.endTimeObserver) { NSNotificationCenter.defaultCenter.removeObserver(this.endTimeObserver); this.endTimeObserver = null; } if (this.playerItem) { this.playerItem.removeObserverForKeyPathContext(this, "status", null); this.playerItem = null; } if (this.avPlayer) { this.avPlayer.pause(); this.avPlayer = null; } } }
四、常见崩溃原因排查
- 忘记移除观察者:如果组件销毁后观察者还存在,当AVPlayerItem触发事件时会访问已销毁的组件实例,直接引发崩溃——所以
cleanup方法一定要在组件卸载时调用 - 空指针访问:比如在
initPlayer前就调用了avPlayer.play(),或者playerItem未初始化就添加观察者,一定要确保实例创建完成后再操作 - KVO使用不当:KVO的回调方法
observeValueForKeyPathOfObjectChangeContext必须正确实现,否则会触发系统异常
内容的提问来源于stack exchange,提问作者Ken
相关产品推荐
相关产品推荐

