bluefireteam/audioplayers

Flutter integration testing audioplayers example app on Windows works with version 5.2.0 but fails with version 6.1.0

Opened this issue · 2 comments

Checklist

  • I read the troubleshooting guide before raising this issue
  • I made sure that the issue I am raising doesn't already exist

Current bug behaviour

The integration test tap on the play button and 10 seconds later tap on the pause button. With audioplayers 5.2.0, this works, but with audioplayers 6.1.0, after playing has started, tapping on pause button does not work.

Expected behaviour

I have many integration tests verifying the correctness of my Flutter application. But all those tests if they make audioplayers start playing must be executed with the audioplayers old version (5.2.0). This is totally uncomprehensible !

Steps to reproduce

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:flutter/material.dart';
import 'package:audiolearn/tools/audioplayers_example.dart' as app;

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

group('Audio Player Integration Test', () {
testWidgets('Play and Pause Test', (WidgetTester tester) async {
// Launch the app
app.main();
await tester.pumpAndSettle();

  // Find the play button
  final playButton = find.byIcon(Icons.play_arrow);
  expect(playButton, findsOneWidget);

  // Tap the play button
  await tester.tap(playButton);
  await tester.pumpAndSettle();

  await Future.delayed(const Duration(seconds: 10));
  await tester.pumpAndSettle();

  // Find the pause button (after playing)
  final pauseButton = find.byIcon(Icons.pause);
  expect(pauseButton, findsOneWidget);

  // Tap the pause button
  await tester.tap(pauseButton);
  await tester.pumpAndSettle();

  // Assert that the pause button is disabled after pausing
  expect(playButton, findsOneWidget);
});

});
}

Code sample

import 'dart:async';
import 'package:path/path.dart' as path;

import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';

void main() {
runApp(const MaterialApp(home: _SimpleExampleApp()));
}

class _SimpleExampleApp extends StatefulWidget {
const _SimpleExampleApp();

@OverRide
_SimpleExampleAppState createState() => _SimpleExampleAppState();
}

class _SimpleExampleAppState extends State<_SimpleExampleApp> {
late AudioPlayer player = AudioPlayer();

@OverRide
void initState() {
super.initState();

// Create the audio player.
player = AudioPlayer();

// Set the release mode to keep the source after playback has completed.
player.setReleaseMode(ReleaseMode.stop);

// Start the player as soon as the app is displayed.
WidgetsBinding.instance.addPostFrameCallback((_) async {
  await player.setSource(DeviceFileSource("C:${path.separator}temp${path.separator}Organ_Voluntary_in_G_Major,_Op._7,_No._9-_I._Largo_Staccato.MP3"));
  await player.resume();
});

}

@OverRide
void dispose() {
// Release all sources and dispose the player.
player.dispose();

super.dispose();

}

@OverRide
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Player'),
),
body: PlayerWidget(player: player),
);
}
}

// The PlayerWidget is a copy of "/lib/components/player_widget.dart".
//#region PlayerWidget

class PlayerWidget extends StatefulWidget {
final AudioPlayer player;

const PlayerWidget({
required this.player,
super.key,
});

@OverRide
State createState() {
return _PlayerWidgetState();
}
}

class _PlayerWidgetState extends State {
PlayerState? _playerState;
Duration? _duration;
Duration? _position;

StreamSubscription? _durationSubscription;
StreamSubscription? _positionSubscription;
StreamSubscription? _playerCompleteSubscription;
StreamSubscription? _playerStateChangeSubscription;

bool get _isPlaying => _playerState == PlayerState.playing;

bool get _isPaused => _playerState == PlayerState.paused;

String get _durationText => _duration?.toString().split('.').first ?? '';

String get _positionText => _position?.toString().split('.').first ?? '';

AudioPlayer get player => widget.player;

@OverRide
void initState() {
super.initState();
// Use initial values from player
_playerState = player.state;
player.getDuration().then(
(value) => setState(() {
_duration = value;
}),
);
player.getCurrentPosition().then(
(value) => setState(() {
_position = value;
}),
);
_initStreams();
}

@OverRide
void setState(VoidCallback fn) {
// Subscriptions only can be closed asynchronously,
// therefore events can occur after widget has been disposed.
if (mounted) {
super.setState(fn);
}
}

@OverRide
void dispose() {
_durationSubscription?.cancel();
_positionSubscription?.cancel();
_playerCompleteSubscription?.cancel();
_playerStateChangeSubscription?.cancel();
super.dispose();
}

@OverRide
Widget build(BuildContext context) {
final color = Theme.of(context).primaryColor;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
key: const Key('play_button'),
onPressed: _isPlaying ? null : _play,
iconSize: 48.0,
icon: const Icon(Icons.play_arrow),
color: color,
),
IconButton(
key: const Key('pause_button'),
onPressed: _isPlaying ? _pause : null,
iconSize: 48.0,
icon: const Icon(Icons.pause),
color: color,
),
IconButton(
key: const Key('stop_button'),
onPressed: _isPlaying || _isPaused ? _stop : null,
iconSize: 48.0,
icon: const Icon(Icons.stop),
color: color,
),
],
),
Slider(
onChanged: (value) {
final duration = _duration;
if (duration == null) {
return;
}
final position = value * duration.inMilliseconds;
player.seek(Duration(milliseconds: position.round()));
},
value: (_position != null &&
_duration != null &&
_position!.inMilliseconds > 0 &&
_position!.inMilliseconds < _duration!.inMilliseconds)
? _position!.inMilliseconds / _duration!.inMilliseconds
: 0.0,
),
Text(
_position != null
? '$_positionText / $_durationText'
: _duration != null
? _durationText
: '',
style: const TextStyle(fontSize: 16.0),
),
],
);
}

void _initStreams() {
_durationSubscription = player.onDurationChanged.listen((duration) {
setState(() => _duration = duration);
});

_positionSubscription = player.onPositionChanged.listen(
  (p) => setState(() => _position = p),
);

_playerCompleteSubscription = player.onPlayerComplete.listen((event) {
  setState(() {
    _playerState = PlayerState.stopped;
    _position = Duration.zero;
  });
});

_playerStateChangeSubscription =
    player.onPlayerStateChanged.listen((state) {
  setState(() {
    _playerState = state;
  });
});

}

Future _play() async {
await player.resume();
setState(() => _playerState = PlayerState.playing);
}

Future _pause() async {
await player.pause();
setState(() => _playerState = PlayerState.paused);
}

Future _stop() async {
await player.stop();
setState(() {
_playerState = PlayerState.stopped;
_position = Duration.zero;
});
}
}

Affected platforms

Windows

Platform details

Windows 10, Visual Studio Code.

AudioPlayers Version

audioplayers 6.1.0

Build mode

debug

Audio Files/URLs/Sources

No response

Screenshots

Organ_Voluntary_in_G_Major,_Op._7,_No._9-_I._Largo_Staccato.zip

Logs

my relevant logs
Full Logs
my full logs or a link to a gist

Flutter doctor:

Output of: flutter doctor -v

Related issues / more information

No response

Working on PR

no way

I have all versions of audioplayers working on Android Studio 2023 and Windows 10. Version 6.1.0 also works.
But on Linux everything is sad. The chain of components differs in version. Replacing versions leads to Linux not loading.
p.s.
I found solution for Ubuntu 20.04 (Terminal, Synaptic and NO Downgrade from Secure. And all will be OK!)
Also i found solution for musical application with Notaton and Key-buttons:

  1. resolve Memory Leak in Windows (use Map of Audioplayers <String, AudioPlayer> , each holds it's own Note)
  2. endless growing of Volume Regulators number in Linux cause sound stop (use method Release() for element of Map of 5...10 Audioplayers. Sounds the latest element. The first element will be Released. ) So we have only 4...5 Volume Regulators for 5 last sounding Notes in Ubuntu->Settings->Sound window. It's OK. NO Dispose() method AT ALL!

"The 'xyz.luan/audioplayers/events/30397293-c286-43db-8767-17482e66e6b2' channel sent a message from native to Flutter on a non-platform thread. Platform channel messages must be sent on the platform thread. Failure to do so may result in data loss or crashes, and must be fixed in the plugin or application code creating that channel" this message does not affect its operation. It continues to work properly.

This error fills all debug output, but does not affect on it's correct work.