1.添加通知栏媒体控制
2.下一首上一首增加防抖 3.集成fb 4.首页单曲增加下载 5.增加后台播放(iOS仍有问题) 6.修改系统状态栏颜色
This commit is contained in:
parent
caded892d9
commit
063e3d7c91
@ -9,8 +9,14 @@
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"
|
||||
android:minSdkVersion = "33"/>
|
||||
|
||||
<!-- ADD THESE TWO PERMISSIONS -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<!-- ALSO ADD THIS PERMISSION IF TARGETING SDK 34 -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
|
||||
|
||||
<application
|
||||
android:label="ToneSnap"
|
||||
android:label="@string/app_name"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon"
|
||||
android:usesCleartextTraffic="true"
|
||||
@ -25,7 +31,8 @@
|
||||
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"
|
||||
tools:ignore="Instantiatable">
|
||||
<!-- 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
|
||||
@ -39,6 +46,26 @@
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- ADD THIS "SERVICE" element -->
|
||||
<service android:name="com.ryanheise.audioservice.AudioService"
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
android:exported="true"
|
||||
tools:ignore="Instantiatable">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!-- ADD THIS "RECEIVER" element -->
|
||||
<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>
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
@ -49,6 +76,10 @@
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
||||
android:value="ca-app-pub-5684307632319406~5753747604"/>
|
||||
|
||||
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
|
||||
<meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/>
|
||||
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
package com.tone.music.offline
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import com.ryanheise.audioservice.AudioServiceActivity
|
||||
|
||||
class MainActivity: FlutterActivity()
|
||||
class MainActivity: AudioServiceActivity()
|
||||
|
||||
6
android/app/src/main/res/values/strings.xml
Normal file
6
android/app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">ToneSnap</string>
|
||||
<string name="facebook_app_id">1058126742561467</string>
|
||||
<string name="facebook_client_token">d37b13dd56db288fe976782b2a10b4a7</string>
|
||||
</resources>
|
||||
@ -490,7 +490,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 15;
|
||||
CURRENT_PROJECT_VERSION = 17;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = UH427LWP22;
|
||||
ENABLE_BITCODE = NO;
|
||||
@ -689,7 +689,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 15;
|
||||
CURRENT_PROJECT_VERSION = 17;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = UH427LWP22;
|
||||
ENABLE_BITCODE = NO;
|
||||
@ -723,7 +723,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 15;
|
||||
CURRENT_PROJECT_VERSION = 17;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = UH427LWP22;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
||||
@ -36,6 +36,45 @@
|
||||
<string>We need access to the photo library to pick audio files.</string>
|
||||
<key>NSUserTrackingUsageDescription</key>
|
||||
<string>We need your permission to access the advertising identifier to provide better ad services.</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>fb1044438596577354</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>FacebookAppID</key>
|
||||
<string>1044438596577354</string>
|
||||
<key>FacebookClientToken</key>
|
||||
<string>4d24452ca73a8fc8157f7a46d4d1930b</string>
|
||||
<key>FacebookDisplayName</key>
|
||||
<string>ToneSnap-ios</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>SKAdNetworkItems</key>
|
||||
<array>
|
||||
<dict>
|
||||
@ -235,29 +274,5 @@
|
||||
<string>3qcr597p9d.skadnetwork</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -10,6 +10,7 @@ import 'package:tone_snap/data/models/ad_config_model.dart';
|
||||
import 'package:tone_snap/data/storage/music_box.dart';
|
||||
import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
|
||||
import 'package:tone_snap/modules/launch/launch_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
|
||||
import 'package:tone_snap/utils/log_util.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
|
||||
@ -35,6 +36,9 @@ class InterstitialAdManager {
|
||||
/// 是否显示
|
||||
bool isShowingAd = false;
|
||||
|
||||
/// 广告显示前的,音乐是否在播放
|
||||
bool showAdFrontIsPlaying = false;
|
||||
|
||||
/// 加载所有插页广告位
|
||||
void loadAdAll() {
|
||||
AdConfigModel adConfigModel = MusicBox().getAdConfig();
|
||||
@ -90,6 +94,11 @@ class InterstitialAdManager {
|
||||
|
||||
/// 加载失败处理
|
||||
void _loadFailHandle(String adUnitId, String adScenes) {
|
||||
int interval = 0;
|
||||
if (adScenes != AdScenes.coldLoading.name && adScenes != AdScenes.hotLoading.name) {
|
||||
interval = 15;
|
||||
}
|
||||
Future.delayed(Duration(seconds: interval), () {
|
||||
AdConfigModel adConfigModel = MusicBox().getAdConfig();
|
||||
var adList = _getAdList(adScenes);
|
||||
if (adList != null) {
|
||||
@ -117,6 +126,7 @@ class InterstitialAdManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
List<AdModel>? _getAdList(String adScenes) {
|
||||
@ -197,6 +207,11 @@ class InterstitialAdManager {
|
||||
onAdShowedFullScreenContent: (ad) {
|
||||
LogUtil.d('$ad onAdShowedFullScreenContent');
|
||||
isShowingAd = true;
|
||||
|
||||
showAdFrontIsPlaying = MusicPlayerController.to.isPlaying.value;
|
||||
if (showAdFrontIsPlaying) {
|
||||
MusicPlayerController.to.pause();
|
||||
}
|
||||
},
|
||||
// 更适合用于跟踪广告展示的次数和效果,以及执行与广告展示相关的操作
|
||||
onAdImpression: (ad) {
|
||||
@ -217,11 +232,21 @@ class InterstitialAdManager {
|
||||
onAdDismissedFullScreenContent: (ad) {
|
||||
LogUtil.d('$ad onAdDismissedFullScreenContent');
|
||||
isShowingAd = false;
|
||||
if (showAdFrontIsPlaying) {
|
||||
MusicPlayerController.to.play();
|
||||
}
|
||||
_closeDateTime = DateTime.now();
|
||||
ad.dispose();
|
||||
|
||||
int interval = 0;
|
||||
if (adScenes != AdScenes.coldLoading.name && adScenes != AdScenes.hotLoading.name) {
|
||||
interval = 15;
|
||||
}
|
||||
Future.delayed(Duration(seconds: interval), () {
|
||||
loadAd(_interstitialAdMap[adScenes]?.adUnitId, adScenes);
|
||||
_interstitialAdMap[adScenes] = null;
|
||||
_interstitialAdMap.remove(adScenes);
|
||||
});
|
||||
|
||||
if(onTap != null) onTap();
|
||||
},
|
||||
|
||||
@ -17,14 +17,16 @@ class ViewStateWidget extends StatelessWidget {
|
||||
super.key,
|
||||
required this.viewState,
|
||||
required this.child,
|
||||
this.cpiBgColor,
|
||||
this.loadColor,
|
||||
this.loadBgColor,
|
||||
this.showTryAgain = false,
|
||||
this.onTapTryAgain,
|
||||
});
|
||||
|
||||
final ViewState viewState;
|
||||
final Widget child;
|
||||
final Color? cpiBgColor;
|
||||
final Color? loadColor;
|
||||
final Color? loadBgColor;
|
||||
final bool showTryAgain;
|
||||
final Function()? onTapTryAgain;
|
||||
|
||||
@ -34,7 +36,7 @@ class ViewStateWidget extends StatelessWidget {
|
||||
case ViewState.normal:
|
||||
return child;
|
||||
case ViewState.loading:
|
||||
return loadingView(backgroundColor: cpiBgColor);
|
||||
return loadingView(loadColor: loadColor, loadBgColor: loadBgColor);
|
||||
case ViewState.empty:
|
||||
return AppConfig.appSideEnum == AppSideEnum.sideA ? emptyViewA() : emptyViewB(
|
||||
showTryAgain: showTryAgain,
|
||||
@ -48,14 +50,14 @@ class ViewStateWidget extends StatelessWidget {
|
||||
|
||||
/// 加载中视图
|
||||
Widget loadingView({
|
||||
Color? color,
|
||||
Color? backgroundColor,
|
||||
Color? loadColor,
|
||||
Color? loadBgColor,
|
||||
}) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.w,
|
||||
color: AppConfig.appSideEnum == AppSideEnum.sideA ? color : seedColor,
|
||||
backgroundColor: backgroundColor,
|
||||
color: AppConfig.appSideEnum == AppSideEnum.sideB ? loadColor ?? seedColor : loadColor,
|
||||
backgroundColor: loadBgColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
2
lib/data/cache/music_cache_manager.dart
vendored
2
lib/data/cache/music_cache_manager.dart
vendored
@ -11,7 +11,7 @@ class MusicCacheManager {
|
||||
Config(
|
||||
key,
|
||||
stalePeriod: const Duration(days: 7),
|
||||
maxNrOfCacheObjects: 30,
|
||||
maxNrOfCacheObjects: 100,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
17
lib/facebook/facebook_manager.dart
Normal file
17
lib/facebook/facebook_manager.dart
Normal file
@ -0,0 +1,17 @@
|
||||
// Author: fengshengxiong
|
||||
// Date: 2024/6/26
|
||||
// Description: facebook管理
|
||||
|
||||
import 'package:facebook_app_events/facebook_app_events.dart';
|
||||
import 'package:tone_snap/utils/log_util.dart';
|
||||
|
||||
class FacebookManager {
|
||||
static Future<void> init() async {
|
||||
try {
|
||||
await FacebookAppEvents().setAdvertiserTracking(enabled: true);
|
||||
await FacebookAppEvents().setAutoLogAppEventsEnabled(true);
|
||||
} catch (e) {
|
||||
LogUtil.d(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,10 +10,10 @@ import 'package:tone_snap/utils/log_util.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
|
||||
class FirebaseRemoteConfigManager {
|
||||
static Future<void> getAll() async {
|
||||
static Future<void> getAllConfig() async {
|
||||
final remoteConfig = FirebaseRemoteConfig.instance;
|
||||
await remoteConfig.setConfigSettings(RemoteConfigSettings(
|
||||
fetchTimeout: const Duration(minutes: 1),
|
||||
fetchTimeout: const Duration(seconds: 30),
|
||||
minimumFetchInterval: Duration.zero,
|
||||
));
|
||||
bool result = await remoteConfig.fetchAndActivate();
|
||||
|
||||
@ -30,13 +30,13 @@ class AppConfig {
|
||||
"coldLoading": [
|
||||
{
|
||||
"level": 1,
|
||||
"identifier": "ca-app-pub-5684307632319406/5757639118",
|
||||
"identifier": "",
|
||||
"ad": "AdMob",
|
||||
"type": "Insert"
|
||||
},
|
||||
{
|
||||
"level": 2,
|
||||
"identifier": "ca-app-pub-5684307632319406/5852437064",
|
||||
"identifier": "",
|
||||
"ad": "AdMob",
|
||||
"type": "Insert"
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ class NetworkConnectivityService extends GetxService {
|
||||
if (!isExecutedTask) {
|
||||
isExecutedTask = true;
|
||||
|
||||
FirebaseRemoteConfigManager.getAll();
|
||||
FirebaseRemoteConfigManager.getAllConfig();
|
||||
|
||||
InterstitialAdManager().loadAdAll();
|
||||
// LibraryNativeAdManager().loadNativeAd();
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -8,8 +6,10 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
import 'package:just_audio_background/just_audio_background.dart';
|
||||
import 'package:tone_snap/components/base_easyloading.dart';
|
||||
import 'package:tone_snap/data/storage/hive_storage.dart';
|
||||
import 'package:tone_snap/facebook/facebook_manager.dart';
|
||||
import 'package:tone_snap/firebase/firebase_crashlytics_manager.dart';
|
||||
import 'package:tone_snap/firebase/firebase_options.dart';
|
||||
import 'package:tone_snap/global/app_config.dart';
|
||||
@ -25,6 +25,9 @@ import 'package:tone_snap/utils/log_util.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// 初始化Hive
|
||||
await initHive();
|
||||
|
||||
// 初始化Firebase
|
||||
try {
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
@ -35,6 +38,8 @@ void main() async {
|
||||
LogUtil.e("Firebase initialization error: $e");
|
||||
}
|
||||
|
||||
await FacebookManager.init();
|
||||
|
||||
// 初始化广告 SDK
|
||||
try {
|
||||
MobileAds.instance.initialize();
|
||||
@ -42,10 +47,17 @@ void main() async {
|
||||
LogUtil.e("MobileAds initialization error: $e");
|
||||
}
|
||||
|
||||
// 初始化Hive
|
||||
await initHive();
|
||||
|
||||
runApp(const MyApp());
|
||||
try {
|
||||
await JustAudioBackground.init(
|
||||
androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio',
|
||||
androidNotificationChannelName: 'Audio playback',
|
||||
androidNotificationIcon: 'mipmap/launcher_icon',
|
||||
// androidNotificationOngoing: true,
|
||||
androidStopForegroundOnPause: false,
|
||||
);
|
||||
} catch (e) {
|
||||
LogUtil.e(e.toString());
|
||||
}
|
||||
|
||||
// 竖屏
|
||||
SystemChrome.setPreferredOrientations([
|
||||
@ -53,14 +65,16 @@ void main() async {
|
||||
DeviceOrientation.portraitDown,
|
||||
]);
|
||||
|
||||
// 设置安卓状态栏背景色透明、底部导航栏颜色和导航栏图标亮度
|
||||
if (Platform.isAndroid) {
|
||||
// 状态栏设置
|
||||
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarBrightness: Brightness.dark,
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
systemNavigationBarColor: Colors.black,
|
||||
systemNavigationBarIconBrightness: Brightness.light,
|
||||
));
|
||||
}
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
|
||||
@ -92,9 +92,9 @@ class LaunchController extends GetxController with GetSingleTickerProviderStateM
|
||||
onTap: () {
|
||||
AppConfig.appSideEnum = AppSideEnum.sideA;
|
||||
Get.offNamed(AppRoutes.initialA);
|
||||
FirebaseAnalyticsManager.logJumpEvent('A', '版本相同,开关关闭');
|
||||
},
|
||||
);
|
||||
FirebaseAnalyticsManager.logJumpEvent('A', '版本相同,开关关闭');
|
||||
}
|
||||
|
||||
void _openSideB(String reason) {
|
||||
@ -105,8 +105,8 @@ class LaunchController extends GetxController with GetSingleTickerProviderStateM
|
||||
MainController.to.changeTheme();
|
||||
Get.offNamed(AppRoutes.initialB);
|
||||
MusicBox().putIsOpenedSideB(true);
|
||||
FirebaseAnalyticsManager.logJumpEvent('B', reason);
|
||||
},
|
||||
);
|
||||
FirebaseAnalyticsManager.logJumpEvent('B', reason);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,9 +8,12 @@ import 'dart:io';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:just_audio_background/just_audio_background.dart';
|
||||
import 'package:tone_snap/components/base_easyloading.dart';
|
||||
import 'package:tone_snap/global/app_config.dart';
|
||||
import 'package:tone_snap/utils/audio_util.dart';
|
||||
import 'package:tone_snap/utils/log_util.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
|
||||
class PlayerController extends GetxController {
|
||||
static PlayerController get to => Get.put(PlayerController());
|
||||
@ -45,11 +48,15 @@ class PlayerController extends GetxController {
|
||||
if (this.filePath != filePath) {
|
||||
this.filePath = filePath;
|
||||
try {
|
||||
final mediaItem = MediaItem(
|
||||
id: filePath,
|
||||
title: AppConfig.appName,
|
||||
);
|
||||
isReady = false;
|
||||
if (filePath.contains('assets')) {
|
||||
duration.value = await _player.setAsset(filePath) ?? Duration.zero;
|
||||
duration.value = await _player.setAsset(filePath, tag: mediaItem) ?? Duration.zero;
|
||||
} else {
|
||||
duration.value = await _player.setFilePath(filePath) ?? Duration.zero;
|
||||
duration.value = await _player.setFilePath(filePath, tag: mediaItem) ?? Duration.zero;
|
||||
}
|
||||
isReady = true;
|
||||
} on PlayerException catch (e) {
|
||||
|
||||
@ -5,8 +5,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:just_audio_background/just_audio_background.dart';
|
||||
import 'package:tone_snap/components/base_easyloading.dart';
|
||||
import 'package:tone_snap/data/api/music_api.dart';
|
||||
import 'package:tone_snap/data/cache/music_cache_manager.dart';
|
||||
@ -17,7 +19,6 @@ import 'package:tone_snap/data/storage/music_box.dart';
|
||||
import 'package:tone_snap/data/storage/offline_box.dart';
|
||||
import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
|
||||
import 'package:tone_snap/modules/sideb/music_bar/music_bar.dart';
|
||||
import 'package:tone_snap/routes/app_routes.dart';
|
||||
import 'package:tone_snap/utils/audio_util.dart';
|
||||
import 'package:tone_snap/utils/log_util.dart';
|
||||
import 'package:tone_snap/utils/num_util.dart';
|
||||
@ -106,8 +107,15 @@ class MusicPlayerController extends GetxController {
|
||||
if (playlist.isNotEmpty) {
|
||||
FirebaseAnalyticsManager.logPlayerBpv(getMusicModel()?.videoId, getMusicModel()?.title, getMusicModel()?.subtitle);
|
||||
resetPlaybackStatus();
|
||||
Get.currentRoute == AppRoutes.playPage ? MusicBar().hide() : MusicBar().show();
|
||||
// Get.currentRoute == AppRoutes.playPage ? MusicBar().hide() : MusicBar().show();
|
||||
try {
|
||||
final mediaItem = MediaItem(
|
||||
id: ObjUtil.getStr(getMusicModel()?.videoId),
|
||||
artist: ObjUtil.getStr(getMusicModel()?.subtitle),
|
||||
title: ObjUtil.getStr(getMusicModel()?.title),
|
||||
artUri: Uri.parse(ObjUtil.getStr(getMusicModel()?.coverUrl)),
|
||||
);
|
||||
|
||||
DateTime startLoadDateTime = DateTime.now();
|
||||
var model = OfflineBox().getList().firstWhereOrNull((e) => e.videoId == getMusicModel()?.videoId);
|
||||
if (model != null && ObjUtil.isNotEmpty(model.localPath)) {
|
||||
@ -118,7 +126,7 @@ class MusicPlayerController extends GetxController {
|
||||
FirebaseAnalyticsManager.logPlayerBFailAction('本地文件不存在');
|
||||
return;
|
||||
}
|
||||
await _player.setFilePath(model.localPath!);
|
||||
await _player.setFilePath(model.localPath!, tag: mediaItem);
|
||||
} else {
|
||||
// 无下载
|
||||
var fileInfo = await MusicCacheManager.checkCache(getMusicModel()?.videoId);
|
||||
@ -126,14 +134,14 @@ class MusicPlayerController extends GetxController {
|
||||
// 有缓存
|
||||
LogUtil.d('读取缓存路径=${fileInfo.file.path}');
|
||||
// 如果有缓存,使用缓存文件
|
||||
await _player.setFilePath(fileInfo.file.path);
|
||||
await _player.setFilePath(fileInfo.file.path, tag: mediaItem);
|
||||
} else {
|
||||
// 无缓存
|
||||
final url = await _getMusicUrl();
|
||||
if (ObjUtil.isEmpty(url)) {
|
||||
return;
|
||||
}
|
||||
await _player.setUrl(url!);
|
||||
await _player.setUrl(url!, tag: mediaItem);
|
||||
// 同时启动缓存下载
|
||||
_cacheManager.downloadFile(url, key: MusicCacheManager.getCacheKey(getMusicModel()!.videoId!)).then((fileInfo) {
|
||||
LogUtil.d('缓存下载路径=${fileInfo.file.path}');
|
||||
@ -175,6 +183,10 @@ class MusicPlayerController extends GetxController {
|
||||
|
||||
/// 监听
|
||||
void _addListening() {
|
||||
_player.playbackEventStream.listen((event) {},
|
||||
onError: (Object e, StackTrace stackTrace) {
|
||||
LogUtil.e('A stream error occurred: $e');
|
||||
});
|
||||
_durationSubscription = _player.durationStream.listen((duration) {
|
||||
totalDuration.value = duration ?? Duration.zero;
|
||||
});
|
||||
@ -265,18 +277,33 @@ class MusicPlayerController extends GetxController {
|
||||
}
|
||||
|
||||
/// 播放/暂停
|
||||
Future<void> playPause() async {
|
||||
void playPause() {
|
||||
if (_player.playing) {
|
||||
_player.pause();
|
||||
pause();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
}
|
||||
|
||||
/// 播放
|
||||
void play() {
|
||||
if (processingState.value == ProcessingState.ready) {
|
||||
_player.play();
|
||||
}
|
||||
}
|
||||
|
||||
/// 暂停
|
||||
void pause() {
|
||||
_player.pause();
|
||||
}
|
||||
|
||||
/// 上一首
|
||||
Future<void> previousTrack() async {
|
||||
// 通过防抖防止在音频源之间快速切换会导致PlatformException
|
||||
EasyDebounce.debounce(
|
||||
'cutTheSong',
|
||||
const Duration(milliseconds: 300),
|
||||
() {
|
||||
if (playlist.length > 1) {
|
||||
switch(playMode.value) {
|
||||
case PlayMode.listLoop:
|
||||
@ -308,10 +335,17 @@ class MusicPlayerController extends GetxController {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 下一首
|
||||
Future<void> nextTrack({bool manualSwitch = true}) async {
|
||||
// 通过防抖防止在音频源之间快速切换会导致PlatformException
|
||||
EasyDebounce.debounce(
|
||||
'cutTheSong',
|
||||
const Duration(milliseconds: 300),
|
||||
() {
|
||||
if (playlist.length > 1) {
|
||||
switch(playMode.value) {
|
||||
case PlayMode.listLoop:
|
||||
@ -332,6 +366,8 @@ class MusicPlayerController extends GetxController {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 在列表范围内生成一个不包括当前索引的随机数
|
||||
|
||||
@ -100,26 +100,34 @@ class MusicBarView extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: 13.w),
|
||||
SizedBox(width: 3.w),
|
||||
Obx(() {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: musicPlayerController.playPause,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10).w,
|
||||
child: Image.asset(
|
||||
musicPlayerController.isPlaying.value ? Assets.sideBPausePlay : Assets.sideBStartPlay,
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(width: 26.w),
|
||||
SizedBox(width: 6.w),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: musicPlayerController.nextTrack,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10).w,
|
||||
child: Image.asset(
|
||||
Assets.sideBMusicBarNext,
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -108,15 +108,15 @@ class SearchMusicView extends GetView<SearchMusicController> {
|
||||
spacing: 8.w,
|
||||
runSpacing: 8.h,
|
||||
children: controller.historyList.map((history) {
|
||||
return InkWell(
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(30).r,
|
||||
child: Material(
|
||||
color: const Color(0xFF1F1F1F),
|
||||
child: InkWell(
|
||||
onTap: () => controller.onTapHistoryItem(history),
|
||||
child: IntrinsicWidth(
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1F1F1F),
|
||||
borderRadius: BorderRadius.circular(30).r,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
history,
|
||||
@ -127,6 +127,8 @@ class SearchMusicView extends GetView<SearchMusicController> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
|
||||
@ -2,13 +2,23 @@
|
||||
// Date: 2024/6/21
|
||||
// Description: 首页单曲Item
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:tone_snap/components/base_easyloading.dart';
|
||||
import 'package:tone_snap/components/dialog/remind_dialog.dart';
|
||||
import 'package:tone_snap/components/network_image_widget.dart';
|
||||
import 'package:tone_snap/data/models/music_model.dart';
|
||||
import 'package:tone_snap/data/storage/offline_box.dart';
|
||||
import 'package:tone_snap/generated/assets.dart';
|
||||
import 'package:tone_snap/global/download_manager.dart';
|
||||
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/more_bottom_sheet/more_bottom_sheet_view.dart';
|
||||
import 'package:tone_snap/modules/sideb/offline/offline_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_controller.dart';
|
||||
import 'package:tone_snap/modules/sideb/widgets/music_item_marquee_text.dart';
|
||||
import 'package:tone_snap/res/themes/app_colors.dart';
|
||||
import 'package:tone_snap/utils/obj_util.dart';
|
||||
|
||||
class BrowseItemAtv extends StatelessWidget {
|
||||
@ -31,6 +41,7 @@ class BrowseItemAtv extends StatelessWidget {
|
||||
children: [
|
||||
_buildCover(),
|
||||
_buildContent(),
|
||||
_buildDownload(),
|
||||
_buildMore(),
|
||||
],
|
||||
),
|
||||
@ -57,31 +68,68 @@ class BrowseItemAtv extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
ObjUtil.getStr(musicModel.title),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.sp,
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
return MusicItemMarqueeText(
|
||||
text: musicModel.title,
|
||||
showMarquee: MusicPlayerController.to.getMusicModel()?.videoId == musicModel.videoId && ObjUtil.isNotEmpty(musicModel.videoId),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
ObjUtil.getStr(musicModel.subtitle),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: const Color(0xFF666666),
|
||||
fontSize: 12.sp,
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
return MusicItemMarqueeText(
|
||||
text: musicModel.subtitle,
|
||||
isTitle: false,
|
||||
showMarquee: MusicPlayerController.to.getMusicModel()?.videoId == musicModel.videoId && ObjUtil.isNotEmpty(musicModel.videoId),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDownload() {
|
||||
return ClipOval(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTapDownload,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4).w,
|
||||
child: SizedBox(
|
||||
width: 24.w,
|
||||
height: 24.w,
|
||||
child: GetBuilder<DownloadManager>(
|
||||
id: DownloadManager.to.downloadStateId,
|
||||
builder: (_) {
|
||||
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
|
||||
return Image.asset(Assets.sideBDownloaded);
|
||||
} else {
|
||||
if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued) {
|
||||
return CircularProgressIndicator(
|
||||
color: seedColor,
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
|
||||
return CircularProgressIndicator(
|
||||
value: DownloadManager.to.getMusicModel(musicModel.videoId)?.progress,
|
||||
backgroundColor: seedColor.withOpacity(0.2),
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(seedColor),
|
||||
strokeWidth: 2.w,
|
||||
);
|
||||
} else {
|
||||
return Image.asset(Assets.sideBNotDownload2);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMore() {
|
||||
return ClipOval(
|
||||
child: Material(
|
||||
@ -101,6 +149,34 @@ class BrowseItemAtv extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void onTapDownload() {
|
||||
if (OfflineBox().checkDownloaded(musicModel.videoId)) {
|
||||
Get.dialog(
|
||||
RemindDialog(
|
||||
content: 'Confirm to remove this song?',
|
||||
confirmOnTap: () async {
|
||||
await OfflineBox().delete(musicModel.videoId!);
|
||||
DownloadManager.to.updateDownloadState();
|
||||
BaseEasyLoading.toast('Removed');
|
||||
if (Get.isRegistered<PersonalMusicLibraryController>()) {
|
||||
PersonalMusicLibraryController.to.refreshOffline();
|
||||
}
|
||||
if (Get.isRegistered<OfflineController>()) {
|
||||
OfflineController.to.getOfflineList();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.enqueued
|
||||
|| DownloadManager.to.getMusicModel(musicModel.videoId)?.taskStatus == TaskStatus.running) {
|
||||
DownloadManager.to.cancelDownload(musicModel.videoId);
|
||||
} else {
|
||||
DownloadManager.to.downloadMusic(musicModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onTapMore() {
|
||||
Get.bottomSheet(
|
||||
MoreBottomSheetView(
|
||||
|
||||
@ -18,7 +18,8 @@ class WebPageView extends StatelessWidget {
|
||||
body: Obx(() {
|
||||
return ViewStateWidget(
|
||||
viewState: controller.viewState.value,
|
||||
cpiBgColor: Colors.white,
|
||||
loadColor: Colors.black,
|
||||
loadBgColor: Colors.white,
|
||||
child: WebViewWidget(controller: controller.webViewController),
|
||||
);
|
||||
}),
|
||||
|
||||
@ -8,6 +8,7 @@ class AudioUtil {
|
||||
static Future<void> configAudioSession() async {
|
||||
final session = await AudioSession.instance;
|
||||
await session.configure(const AudioSessionConfiguration.music());
|
||||
await session.setActive(true);
|
||||
// await session.configure(AudioSessionConfiguration(
|
||||
// avAudioSessionCategory: AVAudioSessionCategory.playback,
|
||||
// avAudioSessionCategoryOptions:
|
||||
|
||||
10
pubspec.yaml
10
pubspec.yaml
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.5+15
|
||||
version: 1.0.6+17
|
||||
|
||||
environment:
|
||||
sdk: '>=3.4.1 <4.0.0'
|
||||
@ -77,6 +77,7 @@ dependencies:
|
||||
audio_session: ^0.1.21
|
||||
flutter_sound: ^9.4.18
|
||||
just_audio: ^0.9.39
|
||||
just_audio_background: ^0.0.1-beta.13
|
||||
# ffmpeg_kit_flutter: ^6.0.3
|
||||
ffmpeg_kit_flutter_audio: 6.0.3-LTS
|
||||
|
||||
@ -105,6 +106,9 @@ dependencies:
|
||||
firebase_crashlytics: ^4.0.3
|
||||
firebase_remote_config: ^5.0.3
|
||||
|
||||
# facebook
|
||||
facebook_app_events: ^0.19.2
|
||||
|
||||
# 网络
|
||||
connectivity_plus: ^6.0.4
|
||||
|
||||
@ -114,8 +118,12 @@ dependencies:
|
||||
# 对键盘可见性变化做出反应
|
||||
flutter_keyboard_visibility: ^6.0.0
|
||||
|
||||
# 进度条
|
||||
step_progress_indicator: ^1.0.2
|
||||
|
||||
# 防抖、节流
|
||||
easy_debounce: ^2.0.3
|
||||
|
||||
flutter_launcher_icons:
|
||||
android: "launcher_icon"
|
||||
ios: true
|
||||
|
||||
Loading…
Reference in New Issue
Block a user