2026-05-07 10:06:02 +08:00

116 lines
3.1 KiB
Dart

import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:audioplayers/audioplayers.dart';
import 'package:crypto/crypto.dart';
import 'package:path_provider/path_provider.dart';
import 'proto/svga.pb.dart';
class SVGAAudioLayer {
final AudioPlayer _player = AudioPlayer();
late final AudioEntity audioItem;
late final MovieEntity _videoItem;
bool _isReady = false;
bool _disposed = false;
double _volume = 1.0;
SVGAAudioLayer(this.audioItem, this._videoItem);
Future<void> playAudio({double? volume}) async {
if (_disposed) return;
// Prevent duplicate playback if already playing or preparing to play
if (_isReady || isPlaying()) return;
final audioData = _videoItem.audiosData[audioItem.audioKey];
if (audioData != null) {
// https://github.com/bluefireteam/audioplayers/issues/1782
// If need use Bytes, plz upgrade to audioplayers: ^6.0.0
// BytesSource source = BytesSource(audioData);
final cacheDir = await getApplicationCacheDirectory();
if (_disposed) return;
// Use MD5 hash to ensure unique cache files even when audioKeys collide
// across different SVGA files with different audio content
final audioHash = md5.convert(audioData).toString();
final cacheFile = File(
'${cacheDir.path}/temp_${audioItem.audioKey}_$audioHash.mp3',
);
if (!cacheFile.existsSync()) {
await cacheFile.writeAsBytes(audioData);
if (_disposed) return;
}
try {
_isReady = true;
if (_disposed) {
_isReady = false;
return;
}
// Apply volume before playing
if (volume != null) {
_volume = volume.clamp(0.0, 1.0);
}
await _player.setVolume(_volume);
if (_disposed) {
_isReady = false;
return;
}
await _player.play(DeviceFileSource(cacheFile.path));
_isReady = false;
// I noticed that this logic exists in the iOS code of SVGAPlayer
// but it seems unnecessary.
// _player.seek(Duration(milliseconds: audioItem.startTime.toInt()));
} catch (e) {
_isReady = false;
log('Failed to play audio: $e');
}
}
}
/// Sets the volume for this audio layer.
/// [volume] should be between 0.0 (mute) and 1.0 (max volume).
void setVolume(double volume) {
if (_disposed) return;
_volume = volume.clamp(0.0, 1.0);
_player.setVolume(_volume);
}
void pauseAudio() {
if (_disposed) return;
_player.pause();
}
void resumeAudio() {
if (_disposed) return;
_player.resume();
}
void stopAudio() {
if (_disposed) return;
if (isPlaying() || isPaused()) unawaited(_player.stop());
}
bool isPlaying() {
if (_disposed) return false;
return _player.state == PlayerState.playing;
}
bool isPaused() {
if (_disposed) return false;
return _player.state == PlayerState.paused;
}
Future<void> dispose() async {
if (_disposed) return;
if (isPlaying() || isPaused()) {
await _player.stop();
}
_disposed = true;
await _player.dispose();
}
}