From d3f60bcbe60000ccc577de6041ad1dcb5ca87759 Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Fri, 31 Mar 2023 16:45:06 +0200 Subject: [PATCH] Add audio visualisation in resource --- lib/Components/audio_player.dart | 307 ++++++++++-------- .../Resources/get_element_for_resource.dart | 49 ++- macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.yaml | 2 +- 4 files changed, 219 insertions(+), 143 deletions(-) diff --git a/lib/Components/audio_player.dart b/lib/Components/audio_player.dart index 227cef4..4519cb3 100644 --- a/lib/Components/audio_player.dart +++ b/lib/Components/audio_player.dart @@ -1,48 +1,81 @@ -import 'dart:convert'; import 'dart:typed_data'; -import 'package:audioplayers/audioplayers.dart'; +//import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; -import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; -import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; -import 'package:http/http.dart' as http; -import 'loading_common.dart'; +import 'package:just_audio/just_audio.dart'; -class AudioPlayerContainer extends StatefulWidget { - const AudioPlayerContainer({Key key, this.resourceDTO, this.isAuto}) : super(key: key); - final ResourceDTO resourceDTO; +class AudioPlayerFloatingContainer extends StatefulWidget { + const AudioPlayerFloatingContainer({Key key, this.audioBytes, this.isAuto}) : super(key: key); + + final Uint8List audioBytes; final bool isAuto; @override - State createState() => _AudioPlayerContainerState(); + State createState() => _AudioPlayerFloatingContainerState(); } -class _AudioPlayerContainerState extends State { +class _AudioPlayerFloatingContainerState extends State { AudioPlayer player = AudioPlayer(); Uint8List audiobytes; bool isplaying = false; bool audioplayed = false; int currentpos = 0; int maxduration = 100; + Duration durationAudio; String currentpostlabel = "00:00"; @override void initState() { - //Uint8List base64String = base64Decode(audiobytes); // LOAD DATA + Future.delayed(Duration.zero, () async { - /*Future.delayed(Duration.zero, () async { - player.onDurationChanged.listen((Duration d) { //get the duration of audio + audiobytes = widget.audioBytes; + player.durationStream.listen((Duration d) { //get the duration of audio maxduration = d.inSeconds; - setState(() { - }); + durationAudio = d; }); - player.onAudioPositionChanged.listen((Duration p){ + //player.bufferedPositionStream + + player.positionStream.listen((event) { + if(event != null) { + + currentpos = event.inMilliseconds; //get the current position of playing audio + + //generating the duration label + int shours = Duration(milliseconds:durationAudio.inMilliseconds - currentpos).inHours; + int sminutes = Duration(milliseconds:durationAudio.inMilliseconds - currentpos).inMinutes; + int sseconds = Duration(milliseconds:durationAudio.inMilliseconds - currentpos).inSeconds; + + int rminutes = sminutes - (shours * 60); + int rseconds = sseconds - (sminutes * 60 + shours * 60 * 60); + + String minutesToShow = rminutes < 10 ? '0$rminutes': rminutes.toString(); + String secondsToShow = rseconds < 10 ? '0$rseconds': rseconds.toString(); + + currentpostlabel = "$minutesToShow:$secondsToShow"; + + setState(() { + //refresh the UI + if(currentpos > player.duration.inMilliseconds) { + print("RESET ALL"); + player.stop(); + player.seek(const Duration(seconds: 0)); + isplaying = false; + audioplayed = false; + currentpostlabel = "00:00"; + } + }); + } + }); + + + + /*player.onPositionChanged.listen((Duration p){ currentpos = p.inMilliseconds; //get the current position of playing audio //generating the duration label @@ -61,154 +94,148 @@ class _AudioPlayerContainerState extends State { setState(() { //refresh the UI }); - }); + });*/ if(widget.isAuto) { - player.playBytes(audiobytes); + //player.play(BytesSource(audiobytes)); + await player.setAudioSource(LoadedSource(audiobytes)); + player.play(); setState(() { isplaying = true; audioplayed = true; }); } - });*/ + }); super.initState(); } @override void dispose() { - /*player.stop(); - player.dispose();*/ + player.stop(); + player.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final appContext = Provider.of(context); - Size size = MediaQuery.of(context).size; - return FutureBuilder( - future: getAudio(widget.resourceDTO.id, appContext), - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - print("DOOOONE"); - if(snapshot.data != null) { - print("snapshot.data"); - print(snapshot.data); - //this.player.playBytes(audiobytes); - } - - return Container( - child: Column( - children: [ - - Container( - child: Text(currentpostlabel, style: TextStyle(fontSize: 25),), - ), - - Container( - child: Wrap( - spacing: 10, - children: [ - ElevatedButton.icon( - style: ElevatedButton.styleFrom( - primary: kPrimaryColor, // Background color - ), - onPressed: () async { - print(isplaying); - print(audioplayed); - if(!isplaying && !audioplayed){ - player.playBytes(audiobytes); - //player.playBytes(bytes) - setState(() { - isplaying = true; - audioplayed = true; - }); - }else if(audioplayed && !isplaying){ - player.resume(); - setState(() { - isplaying = true; - audioplayed = true; - }); - }else{ - player.pause(); - setState(() { - isplaying = false; - }); - } - }, - icon: Icon(isplaying?Icons.pause:Icons.play_arrow), - label:Text(isplaying?"Pause":"Play") - ), - - ElevatedButton.icon( - style: ElevatedButton.styleFrom( - primary: kPrimaryColor, // Background color - ), - onPressed: () async { - /*player.stop(); - setState(() { - isplaying = false; - audioplayed = false; - currentpostlabel = "00:00"; - });*/ - }, - icon: Icon(Icons.stop), - label:Text("Stop") - ), - ], - ), - ) - ], - ), - ); - }else if (snapshot.connectionState == ConnectionState.none) { - return Text("No data"); - } else { - return Center( - child: Container( - height: size.height * 0.2, - child: LoadingCommon() - ) - ); + return FloatingActionButton( + backgroundColor: kPrimaryColor.withOpacity(0.7), + onPressed: () async { + print("TODO"); + if(!isplaying && !audioplayed){ + //player.play(BytesSource(audiobytes)); + await player.setAudioSource(LoadedSource(audiobytes)); + player.play(); + setState(() { + isplaying = true; + audioplayed = true; + }); + }else if(audioplayed && !isplaying){ + //player.resume(); + player.play(); + setState(() { + isplaying = true; + audioplayed = true; + }); + }else{ + player.pause(); + setState(() { + isplaying = false; + }); } + }, + child: isplaying ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.pause), + Text(currentpostlabel), + ], + ) : audioplayed ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.play_arrow), + Text(currentpostlabel), + ], + ): const Icon(Icons.play_arrow), - } + /*Column( + children: [ + //Text(currentpostlabel, style: const TextStyle(fontSize: 25)), + Wrap( + spacing: 10, + children: [ + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: kSecondColor, // Background color + ), + onPressed: () async { + if(!isplaying && !audioplayed){ + //player.play(BytesSource(audiobytes)); + await player.setAudioSource(LoadedSource(audiobytes)); + player.play(); + setState(() { + isplaying = true; + audioplayed = true; + }); + }else if(audioplayed && !isplaying){ + //player.resume(); + player.play(); + setState(() { + isplaying = true; + audioplayed = true; + }); + }else{ + player.pause(); + setState(() { + isplaying = false; + }); + } + }, + icon: Icon(isplaying?Icons.pause:Icons.play_arrow), + //label:Text(isplaying?TranslationHelper.getFromLocale("pause", appContext.getContext()):TranslationHelper.getFromLocale("play", appContext.getContext())) + ), + + /*ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: kSecondColor, // Background color + ), + onPressed: () async { + player.stop(); + player.seek(const Duration(seconds: 0)); + setState(() { + isplaying = false; + audioplayed = false; + currentpostlabel = "00:00"; + }); + }, + icon: const Icon(Icons.stop), + //label: Text(TranslationHelper.getFromLocale("stop", appContext.getContext())) + ),*/ + ], + ) + ], + ),*/ ); } } -Future getAudio(String resourceId, AppContext appContext) async { - try { - ManagerAppContext managerAppContext = appContext.getContext() as ManagerAppContext; - var url = managerAppContext.host + "/api/Resource/" + resourceId; // TO TEST TODO UPDATE ROUTE - print("DOWNLOAD AUDDDDIOOOOO ------------"); - print(url); - //HttpClient client2 = HttpClient(); - print("before.. hmmm? "); - var _downloadData = []; +// Feed your own stream of bytes into the player +class LoadedSource extends StreamAudioSource { + final List bytes; + LoadedSource(this.bytes); - print("before.. ? "); - - /*var test = await http.get(Uri.parse(url)); - print("test"); - print(test);*/ - - var test2 = await http.readBytes(Uri.parse(url)); - print("test2"); - print(test2); - - //final HttpClientRequest request = await client2.getUrl(url); - print("RESPONSE"); - /*HttpClientResponse response = await request.close(); - await for(dynamic d in response) { _downloadData.addAll(d); } - print("AFTER");*/ - final base64Str = base64.encode(test2); - Uint8List base64String = base64Decode(base64Str); // LOAD DATA - return base64String; - } catch (e) { - print(e); - print("IN CATCH"); - return null; + @override + Future request([int start, int end]) async { + start ??= 0; + end ??= bytes.length; + return StreamAudioResponse( + sourceLength: bytes.length, + contentLength: end - start, + offset: start, + stream: Stream.value(bytes.sublist(start, end)), + contentType: 'audio/mpeg', + ); } } - diff --git a/lib/Screens/Resources/get_element_for_resource.dart b/lib/Screens/Resources/get_element_for_resource.dart index 3b2c06a..c3e4635 100644 --- a/lib/Screens/Resources/get_element_for_resource.dart +++ b/lib/Screens/Resources/get_element_for_resource.dart @@ -1,8 +1,14 @@ +import 'dart:convert'; +import 'dart:typed_data'; + import 'package:manager_app/Components/audio_player.dart'; +import 'package:manager_app/Components/loading_common.dart'; +import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; getElementForResource(dynamic resourceDTO, AppContext appContext) { switch(resourceDTO.type) { @@ -49,8 +55,32 @@ getElementForResource(dynamic resourceDTO, AppContext appContext) { ); break; case ResourceType.Audio: - //return AudioPlayerContainer(resourceDTO: resourceDTO, isAuto: true); - return Text("Fichier audio - aucune visualisation possible"); + return FutureBuilder( + future: getAudio(resourceDTO.id, appContext), + builder: (context, AsyncSnapshot snapshot) { + Size size = MediaQuery.of(context).size; + if (snapshot.connectionState == ConnectionState.done) { + var audioBytes; + if(snapshot.data != null) { + print("snapshot.data"); + print(snapshot.data); + audioBytes = snapshot.data; + //this.player.playBytes(audiobytes); + } + return AudioPlayerFloatingContainer(audioBytes: audioBytes, isAuto: true); + } else if (snapshot.connectionState == ConnectionState.none) { + return Text("No data"); + } else { + return Center( + child: Container( + height: size.height * 0.2, + child: LoadingCommon() + ) + ); + } + } + ); + //return Text("Fichier audio - aucune visualisation possible"); break; case ResourceType.Video: return Text("Vidéo locale - aucune visualisation possible"); @@ -60,3 +90,18 @@ getElementForResource(dynamic resourceDTO, AppContext appContext) { break; } } + +Future getAudio(String resourceId, AppContext appContext) async { + try { + ManagerAppContext managerAppContext = appContext.getContext() as ManagerAppContext; + var url = managerAppContext.host + "/api/Resource/" + resourceId; // TO TEST TODO UPDATE ROUTE + var test2 = await http.readBytes(Uri.parse(url)); + final base64Str = base64.encode(test2); + Uint8List base64String = base64Decode(base64Str); // LOAD DATA + return base64String; + } catch (e) { + print(e); + print("IN CATCH"); + return null; + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b6b2f76..1e97c54 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,14 @@ import FlutterMacOS import Foundation +import audio_session import audioplayers +import just_audio import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) AudioplayersPlugin.register(with: registry.registrar(forPlugin: "AudioplayersPlugin")) + JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.yaml b/pubspec.yaml index f5b91dc..36fa3c1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,7 +41,7 @@ dependencies: #path_provider: ^2.0.2 encrypt: ^5.0.0 qr_flutter: ^4.0.0 - audioplayers: 0.18.3 + just_audio: ^0.9.31 pdf: ^3.6.0 multi_select_flutter: ^4.1.2 #msix: ^2.1.3