自定义Platform Channel实现iOS音频播放无声音问题排查
iOS自定义Platform Channel播放音频无声音问题
我通过自定义Platform Channel在iOS上播放音频文件,执行播放操作时无报错,但设备/模拟器没有任何声音输出。使用just_audio插件播放同一文件完全正常,因此排除文件路径问题(注释的just_audio代码可验证)。
相关代码
AppDelegate.swift
import UIKit import Flutter import AVFoundation @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let playerChannel = FlutterMethodChannel(name: "PlayerChannel", binaryMessenger: controller.binaryMessenger) playerChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in guard call.method == "play" else { result(FlutterMethodNotImplemented) return } guard let args = call.arguments as? [String : Any] else {return} let text = args["path"] as! String self?.playAudio(result: result, path: text) }) GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } private func playAudio(result: FlutterResult, path: String) { do{ let audioData = try Data.init(contentsOf:URL(fileURLWithPath: path)) try AVAudioSession.sharedInstance().setMode(.default) try AVAudioSession.sharedInstance().setActive(true) let audioPlayer = try AVAudioPlayer.init(data: audioData) audioPlayer.play() } catch { print(String(describing: error)) result(FlutterError(code: "UNAVAILABLE", message: "error.", details: nil)) } } }
Dart调用代码
import 'dart:developer'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:just_audio/just_audio.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, ), home: const HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { static const platform = MethodChannel('PlayerChannel'); Future<String> getPath() async { Directory tempDir = await getTemporaryDirectory(); String tempPath = tempDir.path; var filePath = "$tempPath/brown_noise.mp3"; var file = File(filePath); if (file.existsSync()) { return file.path; } else { final byteData = await rootBundle.load('assets/brown_noise.mp3'); final buffer = byteData.buffer; await file.create(recursive: true); file.writeAsBytes( buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes)); return file.path; } } @override void initState() { super.initState(); Future.delayed(const Duration(milliseconds: 800), () async { try { await platform.invokeMethod('play', {'path': await getPath()}); } on PlatformException catch (e) { log("Failed to play: '${e.message}'."); } /* final a = AudioPlayer(); a.setFilePath(await getPath()); a.play(); */ }); } @override Widget build(BuildContext context) { return const Placeholder(); } }
问题解决及说明
核心问题
- AVAudioPlayer被提前回收:原代码中
audioPlayer是playAudio方法内的局部变量,方法执行完毕后会被iOS的ARC自动释放,导致播放器还未开始播放就被销毁。 - 音频会话配置不全:仅设置会话模式未设置类别,无法确保应用在静音/后台状态下的音频播放权限,也未处理与其他应用的音频会话冲突。
修改后的AppDelegate.swift代码
import UIKit import Flutter import AVFoundation @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { // 将AVAudioPlayer声明为成员变量,避免被ARC回收 private var audioPlayer: AVAudioPlayer? override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller : FlutterViewController = window?.rootViewController as! FlutterViewController let playerChannel = FlutterMethodChannel(name: "PlayerChannel", binaryMessenger: controller.binaryMessenger) playerChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in guard call.method == "play" else { result(FlutterMethodNotImplemented) return } guard let args = call.arguments as? [String : Any], let path = args["path"] as? String else { result(FlutterError(code: "INVALID_ARG", message: "参数错误", details: nil)) return } self?.playAudio(result: result, path: path) }) GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } private func playAudio(result: FlutterResult, path: String) { do { // 设置音频会话类别为playback,确保静音/后台也能播放 try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) // 激活会话并处理冲突 try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation) // 直接通过文件URL初始化,无需转Data let audioUrl = URL(fileURLWithPath: path) audioPlayer = try AVAudioPlayer(contentsOf: audioUrl) guard let player = audioPlayer else { result(FlutterError(code: "INIT_FAILED", message: "播放器初始化失败", details: nil)) return } player.play() result(true) } catch { print("播放错误:\(error.localizedDescription)") result(FlutterError(code: "PLAY_FAILED", message: error.localizedDescription, details: nil)) } } } // 可选:监听播放完成事件 extension AppDelegate: AVAudioPlayerDelegate { func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { print("播放完成:\(flag)") audioPlayer = nil } }
关键修改点
- 将
audioPlayer改为类成员变量,延长生命周期 - 设置
AVAudioSession的类别为.playback,确保音频播放权限 - 直接通过文件URL初始化播放器,避免不必要的Data转换
- 完善参数校验和错误返回,便于调试
内容的提问来源于stack exchange,提问作者artemniy
相关产品推荐
相关产品推荐

