mirror of
https://github.com/nostrlabs-io/zap-stream-flutter.git
synced 2025-06-15 19:48:23 +00:00
feat: background playback (wip)
https://github.com/ryanheise/audio_service/issues/1124
This commit is contained in:
@ -1,5 +1,4 @@
|
|||||||
import com.android.build.api.dsl.ApkSigningConfig
|
import com.android.build.api.dsl.ApkSigningConfig
|
||||||
import com.android.build.api.dsl.SigningConfig
|
|
||||||
import org.jetbrains.kotlin.gradle.targets.js.toHex
|
import org.jetbrains.kotlin.gradle.targets.js.toHex
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
@ -8,11 +7,9 @@ import java.util.Properties
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
// START: FlutterFire Configuration
|
|
||||||
id("com.google.gms.google-services")
|
|
||||||
// END: FlutterFire Configuration
|
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
id("com.google.gms.google-services")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getKeystoreFile(base64String: String?, hash: String, fileName: String): File {
|
fun getKeystoreFile(base64String: String?, hash: String, fileName: String): File {
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
the Flutter tool needs it to communicate with the running application
|
the Flutter tool needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,52 +1,73 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="zap.stream"
|
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="zap.stream">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name="com.ryanheise.audioservice.AudioServiceActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
|
||||||
android:hardwareAccelerated="true"
|
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
|
||||||
the Android process has started. This theme is visible to the user
|
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
|
||||||
to determine the Window background behind the Flutter UI. -->
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme" />
|
||||||
/>
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter android:autoVerify="true">
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="http" android:host="zap.stream" />
|
|
||||||
|
<data android:host="zap.stream" />
|
||||||
|
<data android:scheme="http" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data android:scheme="zswc" />
|
<data android:scheme="zswc" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<service
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
android:name="com.ryanheise.audioservice.AudioService"
|
||||||
|
android:exported="true"
|
||||||
|
android:foregroundServiceType="mediaPlayback"
|
||||||
|
android:permission="android.permission.FOREGROUND_SERVICE"
|
||||||
|
tools:ignore="Instantiatable">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<receiver
|
||||||
|
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
||||||
|
android:exported="true"
|
||||||
|
tools:ignore="Instantiatable">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
||||||
|
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
@ -55,10 +76,11 @@
|
|||||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="firebase_messaging_auto_init_enabled"
|
android:name="firebase_messaging_auto_init_enabled"
|
||||||
android:value="false" />
|
android:value="false" />
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
|
<string>audio</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
|
@ -25,7 +25,11 @@ void runZapStream() {
|
|||||||
supportedLocales: AppLocaleUtils.supportedLocales,
|
supportedLocales: AppLocaleUtils.supportedLocales,
|
||||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||||
theme: ThemeData.localize(
|
theme: ThemeData.localize(
|
||||||
ThemeData(colorScheme: ColorScheme.dark(), highlightColor: PRIMARY_1),
|
ThemeData(
|
||||||
|
colorScheme: ColorScheme.dark(),
|
||||||
|
highlightColor: PRIMARY_1,
|
||||||
|
useMaterial3: true,
|
||||||
|
),
|
||||||
TextTheme(),
|
TextTheme(),
|
||||||
),
|
),
|
||||||
routerConfig: GoRouter(
|
routerConfig: GoRouter(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:developer' as developer;
|
import 'dart:developer' as developer;
|
||||||
|
|
||||||
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
@ -7,6 +8,9 @@ import 'package:zap_stream_flutter/app.dart';
|
|||||||
import 'package:zap_stream_flutter/const.dart';
|
import 'package:zap_stream_flutter/const.dart';
|
||||||
import 'package:zap_stream_flutter/i18n/strings.g.dart';
|
import 'package:zap_stream_flutter/i18n/strings.g.dart';
|
||||||
import 'package:zap_stream_flutter/notifications.dart';
|
import 'package:zap_stream_flutter/notifications.dart';
|
||||||
|
import 'package:zap_stream_flutter/player.dart';
|
||||||
|
|
||||||
|
late final MainPlayer mainPlayer;
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -20,5 +24,14 @@ Future<void> main() async {
|
|||||||
developer.log("Failed to setup notifications: $e");
|
developer.log("Failed to setup notifications: $e");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainPlayer = await AudioService.init(
|
||||||
|
builder: () => MainPlayer(),
|
||||||
|
config: AudioServiceConfig(
|
||||||
|
androidNotificationChannelId: "io.nostrlabs.zap_stream_flutter.player",
|
||||||
|
androidNotificationChannelName: "player",
|
||||||
|
androidNotificationOngoing: true
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
runZapStream();
|
runZapStream();
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import 'package:zap_stream_flutter/widgets/notifications_button.dart';
|
|||||||
import 'package:zap_stream_flutter/widgets/pill.dart';
|
import 'package:zap_stream_flutter/widgets/pill.dart';
|
||||||
import 'package:zap_stream_flutter/widgets/profile.dart';
|
import 'package:zap_stream_flutter/widgets/profile.dart';
|
||||||
import 'package:zap_stream_flutter/widgets/stream_info.dart';
|
import 'package:zap_stream_flutter/widgets/stream_info.dart';
|
||||||
import 'package:zap_stream_flutter/widgets/video_player.dart';
|
import 'package:zap_stream_flutter/widgets/video_player_main.dart';
|
||||||
import 'package:zap_stream_flutter/widgets/zap.dart';
|
import 'package:zap_stream_flutter/widgets/zap.dart';
|
||||||
|
|
||||||
class StreamPage extends StatefulWidget {
|
class StreamPage extends StatefulWidget {
|
||||||
@ -141,11 +141,13 @@ class _StreamPage extends State<StreamPage> with RouteAware {
|
|||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child:
|
child:
|
||||||
(stream.info.stream != null && !_offScreen)
|
(stream.info.stream != null && !_offScreen)
|
||||||
? VideoPlayerWidget(
|
? MainVideoPlayerWidget(
|
||||||
url: stream.info.stream!,
|
url: stream.info.stream!,
|
||||||
placeholder: stream.info.image,
|
placeholder: stream.info.image,
|
||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
isLive: true,
|
isLive: true,
|
||||||
|
title: stream.info.title,
|
||||||
|
|
||||||
)
|
)
|
||||||
: (stream.info.image?.isNotEmpty ?? false)
|
: (stream.info.image?.isNotEmpty ?? false)
|
||||||
? ProxyImg(url: stream.info.image)
|
? ProxyImg(url: stream.info.image)
|
||||||
|
100
lib/player.dart
Normal file
100
lib/player.dart
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:chewie/chewie.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
import 'package:zap_stream_flutter/const.dart';
|
||||||
|
import 'package:zap_stream_flutter/imgproxy.dart';
|
||||||
|
|
||||||
|
class MainPlayer extends BaseAudioHandler {
|
||||||
|
VideoPlayerController? _controller;
|
||||||
|
ChewieController? _chewieController;
|
||||||
|
|
||||||
|
ChewieController? get chewie {
|
||||||
|
return _chewieController;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> play() async {
|
||||||
|
await _chewieController?.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> pause() async {
|
||||||
|
await _chewieController?.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> stop() async {
|
||||||
|
await _chewieController?.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadUrl(
|
||||||
|
String url, {
|
||||||
|
String? title,
|
||||||
|
bool? autoPlay,
|
||||||
|
double? aspectRatio,
|
||||||
|
bool? isLive,
|
||||||
|
String? placeholder,
|
||||||
|
String? artist,
|
||||||
|
}) {
|
||||||
|
if (_chewieController != null) {
|
||||||
|
_chewieController!.dispose();
|
||||||
|
_controller!.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_controller = VideoPlayerController.networkUrl(
|
||||||
|
Uri.parse(url),
|
||||||
|
httpHeaders: Map.from({"user-agent": userAgent}),
|
||||||
|
);
|
||||||
|
_chewieController = ChewieController(
|
||||||
|
videoPlayerController: _controller!,
|
||||||
|
autoPlay: autoPlay ?? true,
|
||||||
|
aspectRatio: aspectRatio,
|
||||||
|
isLive: isLive ?? false,
|
||||||
|
autoInitialize: true,
|
||||||
|
allowedScreenSleep: false,
|
||||||
|
placeholder:
|
||||||
|
(placeholder?.isNotEmpty ?? false)
|
||||||
|
? ProxyImg(url: placeholder!)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
// insert media item
|
||||||
|
mediaItem.add(
|
||||||
|
MediaItem(
|
||||||
|
id: url.hashCode.toString(),
|
||||||
|
title: title ?? url,
|
||||||
|
artist: artist,
|
||||||
|
isLive: _chewieController!.isLive,
|
||||||
|
artUri:
|
||||||
|
(placeholder?.isNotEmpty ?? false) ? Uri.parse(placeholder!) : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_chewieController!.videoPlayerController.addListener(updatePlayerState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updatePlayerState() {
|
||||||
|
final isPlaying =
|
||||||
|
_chewieController?.videoPlayerController.value.isPlaying ?? false;
|
||||||
|
|
||||||
|
playbackState.add(
|
||||||
|
playbackState.value.copyWith(
|
||||||
|
controls: [
|
||||||
|
if (playbackState.value.playing)
|
||||||
|
MediaControl.pause
|
||||||
|
else
|
||||||
|
MediaControl.play,
|
||||||
|
MediaControl.stop,
|
||||||
|
],
|
||||||
|
playing: isPlaying,
|
||||||
|
processingState: switch (_chewieController
|
||||||
|
?.videoPlayerController
|
||||||
|
.value
|
||||||
|
.isInitialized) {
|
||||||
|
true => AudioProcessingState.ready,
|
||||||
|
false => AudioProcessingState.idle,
|
||||||
|
_ => AudioProcessingState.completed,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
53
lib/widgets/video_player_main.dart
Normal file
53
lib/widgets/video_player_main.dart
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import 'package:chewie/chewie.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:zap_stream_flutter/main.dart';
|
||||||
|
|
||||||
|
class MainVideoPlayerWidget extends StatefulWidget {
|
||||||
|
final String url;
|
||||||
|
final String? title;
|
||||||
|
final String? placeholder;
|
||||||
|
final double? aspectRatio;
|
||||||
|
final bool? autoPlay;
|
||||||
|
final bool? isLive;
|
||||||
|
|
||||||
|
const MainVideoPlayerWidget({
|
||||||
|
super.key,
|
||||||
|
required this.url,
|
||||||
|
this.title,
|
||||||
|
this.placeholder,
|
||||||
|
this.aspectRatio,
|
||||||
|
this.autoPlay,
|
||||||
|
this.isLive,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _MainVideoPlayerWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MainVideoPlayerWidget extends State<MainVideoPlayerWidget> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
mainPlayer.loadUrl(
|
||||||
|
widget.url,
|
||||||
|
title: widget.title,
|
||||||
|
placeholder: widget.placeholder,
|
||||||
|
aspectRatio: widget.aspectRatio,
|
||||||
|
autoPlay: widget.autoPlay,
|
||||||
|
isLive: widget.isLive,
|
||||||
|
artist: "zap.stream"
|
||||||
|
);
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
mainPlayer.stop();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Chewie(controller: mainPlayer.chewie!);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,8 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import audio_service
|
||||||
|
import audio_session
|
||||||
import emoji_picker_flutter
|
import emoji_picker_flutter
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import firebase_core
|
import firebase_core
|
||||||
@ -23,6 +25,8 @@ import video_player_avfoundation
|
|||||||
import wakelock_plus
|
import wakelock_plus
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
|
||||||
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
|
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
|
32
pubspec.lock
32
pubspec.lock
@ -49,6 +49,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.12.0"
|
version: "2.12.0"
|
||||||
|
audio_service:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: audio_service
|
||||||
|
sha256: cb122c7c2639d2a992421ef96b67948ad88c5221da3365ccef1031393a76e044
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.18.18"
|
||||||
|
audio_service_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audio_service_platform_interface
|
||||||
|
sha256: "6283782851f6c8b501b60904a32fc7199dc631172da0629d7301e66f672ab777"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3"
|
||||||
|
audio_service_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audio_service_web
|
||||||
|
sha256: b8ea9243201ee53383157fbccf13d5d2a866b5dda922ec19d866d1d5d70424df
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.4"
|
||||||
|
audio_session:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audio_session
|
||||||
|
sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
bech32:
|
bech32:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -23,10 +23,10 @@ dependencies:
|
|||||||
crypto: ^3.0.6
|
crypto: ^3.0.6
|
||||||
convert: ^3.1.2
|
convert: ^3.1.2
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
video_player: ^2.9.5
|
|
||||||
clipboard: ^0.1.3
|
clipboard: ^0.1.3
|
||||||
qr_flutter: ^4.1.0
|
qr_flutter: ^4.1.0
|
||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
|
video_player: ^2.9.5
|
||||||
chewie: ^1.11.3
|
chewie: ^1.11.3
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.1.2
|
||||||
emoji_picker_flutter: ^4.3.0
|
emoji_picker_flutter: ^4.3.0
|
||||||
@ -45,6 +45,7 @@ dependencies:
|
|||||||
flutter_local_notifications: ^19.2.1
|
flutter_local_notifications: ^19.2.1
|
||||||
flutter_dotenv: ^5.2.1
|
flutter_dotenv: ^5.2.1
|
||||||
protocol_handler: ^0.2.0
|
protocol_handler: ^0.2.0
|
||||||
|
audio_service: ^0.18.18
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
ndk:
|
ndk:
|
||||||
|
Reference in New Issue
Block a user