You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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();
  }
}

问题解决及说明

核心问题

  1. AVAudioPlayer被提前回收:原代码中audioPlayer是playAudio方法内的局部变量,方法执行完毕后会被iOS的ARC自动释放,导致播放器还未开始播放就被销毁。
  2. 音频会话配置不全:仅设置会话模式未设置类别,无法确保应用在静音/后台状态下的音频播放权限,也未处理与其他应用的音频会话冲突。

修改后的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 12:15:48