Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: interactive music player #6

Merged
merged 4 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ios/Flutter/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
1 change: 1 addition & 0 deletions ios/Flutter/Release.xcconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
44 changes: 44 additions & 0 deletions ios/Podfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}

def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end

File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

target 'Runner' do
use_frameworks!
use_modular_headers!

flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end

post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
35 changes: 35 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
PODS:
- audio_session (0.0.1):
- Flutter
- Flutter (1.0.0)
- just_audio (0.0.1):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS

DEPENDENCIES:
- audio_session (from `.symlinks/plugins/audio_session/ios`)
- Flutter (from `Flutter`)
- just_audio (from `.symlinks/plugins/just_audio/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)

EXTERNAL SOURCES:
audio_session:
:path: ".symlinks/plugins/audio_session/ios"
Flutter:
:path: Flutter
just_audio:
:path: ".symlinks/plugins/just_audio/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"

SPEC CHECKSUMS:
audio_session: 088d2483ebd1dc43f51d253d4a1c517d9a2e7207
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46

PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796

COCOAPODS: 1.15.2
198 changes: 170 additions & 28 deletions ios/Runner.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions ios/Runner.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 19 additions & 6 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,31 @@ import 'package:aes_ui/aes_ui.dart';
import 'package:airplane_entertainment_system/airplane_entertainment_system/airplane_entertainment_system.dart';
import 'package:airplane_entertainment_system/l10n/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:just_audio/just_audio.dart';
import 'package:music_repository/music_repository.dart';

class App extends StatelessWidget {
const App({super.key});

@override
Widget build(BuildContext context) {
return AesLayout(
child: MaterialApp(
theme: const AesTheme().themeData,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const AirplaneEntertainmentSystemScreen(),
return MultiRepositoryProvider(
providers: [
RepositoryProvider(
create: (context) => MusicRepository(),
),
RepositoryProvider(
create: (context) => AudioPlayer(),
),
],
child: AesLayout(
child: MaterialApp(
theme: const AesTheme().themeData,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const AirplaneEntertainmentSystemScreen(),
),
),
);
}
Expand Down
1 change: 1 addition & 0 deletions lib/music_player/cubit/cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'music_player_cubit.dart';
115 changes: 115 additions & 0 deletions lib/music_player/cubit/music_player_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:just_audio/just_audio.dart';
import 'package:music_repository/music_repository.dart';

part 'music_player_state.dart';

class MusicPlayerCubit extends Cubit<MusicPlayerState> {
MusicPlayerCubit({
required MusicRepository musicRepository,
required AudioPlayer player,
}) : _musicRepository = musicRepository,
_player = player,
super(const MusicPlayerState()) {
_isPlayingSubscription = _player.playingStream.listen(_onIsPlayingChanged);
_progressSubscription = _player.positionStream.listen(_onProgressChanged);
_trackSubscription =
_player.currentIndexStream.listen(_onTrackIndexChanged);
}

final MusicRepository _musicRepository;
late final StreamSubscription<bool> _isPlayingSubscription;
late final StreamSubscription<Duration> _progressSubscription;
late final StreamSubscription<int?> _trackSubscription;
final AudioPlayer _player;

void _onIsPlayingChanged(bool isPlaying) {
emit(state.copyWith(isPlaying: isPlaying));
}

void _onProgressChanged(Duration position) {
final duration = _player.duration;
if (duration == null) return;
final progress = position.inMilliseconds / duration.inMilliseconds;
emit(state.copyWith(progress: progress));
}

void _onTrackIndexChanged(int? index) {
if (index == null) {
emit(MusicPlayerState(tracks: state.tracks));
} else {
emit(state.copyWith(currentTrackIndex: index));
}
}

void initialize() {
final tracks = _musicRepository.getTracks();

if (_player.audioSource == null) {
final playlist = ConcatenatingAudioSource(
children: tracks.map((track) => AudioSource.asset(track.path)).toList(),
);
_player.setAudioSource(playlist);
}

emit(state.copyWith(tracks: tracks));
}

void playTrack(MusicTrack track) {
if (track == state.currentTrack) {
return togglePlayPause();
}
_player
..seek(Duration.zero, index: track.index)
..play();
}

void togglePlayPause() {
if (state.currentTrack == null) return;
if (state.isPlaying) {
_player.pause();
} else {
_player.play();
}
}

void seek(double progress) {
final duration = _player.duration;
if (duration != null) {
_player.seek(duration * progress);
}
}

void next() {
if (state.currentTrack == null) return;
_player.seekToNext();
}

void previous() {
if (state.currentTrack == null) return;
_player.seekToPrevious();
}

void toggleLoop() {
final loop = !state.isLoop;
_player.setLoopMode(loop ? LoopMode.one : LoopMode.off);
emit(state.copyWith(isLoop: !state.isLoop));
}

void toggleShuffle() {
final shuffle = !state.isShuffle;
_player.setShuffleModeEnabled(shuffle);
emit(state.copyWith(isShuffle: !state.isShuffle));
}

@override
Future<void> close() {
_isPlayingSubscription.cancel();
_progressSubscription.cancel();
_trackSubscription.cancel();
return super.close();
}
}
51 changes: 51 additions & 0 deletions lib/music_player/cubit/music_player_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
part of 'music_player_cubit.dart';

class MusicPlayerState extends Equatable {
const MusicPlayerState({
this.tracks = const [],
this.currentTrackIndex,
this.progress = 0.0,
this.isPlaying = false,
this.isLoop = false,
this.isShuffle = false,
});

final List<MusicTrack> tracks;
final int? currentTrackIndex;
final double progress;
final bool isPlaying;
final bool isLoop;
final bool isShuffle;

MusicTrack? get currentTrack => tracks.isNotEmpty && currentTrackIndex != null
? tracks[currentTrackIndex!]
: null;

@override
List<Object?> get props => [
tracks,
currentTrackIndex,
isPlaying,
progress,
isLoop,
isShuffle,
];

MusicPlayerState copyWith({
List<MusicTrack>? tracks,
int? currentTrackIndex,
double? progress,
bool? isPlaying,
bool? isLoop,
bool? isShuffle,
}) {
return MusicPlayerState(
tracks: tracks ?? this.tracks,
currentTrackIndex: currentTrackIndex ?? this.currentTrackIndex,
isPlaying: isPlaying ?? this.isPlaying,
progress: progress ?? this.progress,
isLoop: isLoop ?? this.isLoop,
isShuffle: isShuffle ?? this.isShuffle,
);
}
}
1 change: 1 addition & 0 deletions lib/music_player/music_player.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export 'cubit/cubit.dart';
export 'view/view.dart';
export 'widgets/widgets.dart';
Loading