feat: 新增媒体通知
This commit is contained in:
@ -45,7 +45,7 @@
|
||||
android:fullBackupContent="false"
|
||||
tools:replace="android:allowBackup">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:name="com.ryanheise.audioservice.AudioServiceActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
@ -226,6 +226,24 @@
|
||||
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<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>
|
||||
|
||||
<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
|
||||
@ -238,6 +256,8 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<!--
|
||||
Media access permissions.
|
||||
Android 13 or higher.
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/ic_stat_replay_10.png
Normal file
BIN
android/app/src/main/res/drawable-xxhdpi/ic_stat_replay_10.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_stat_forward_10.png
Normal file
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_stat_forward_10.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_stat_replay_10.png
Normal file
BIN
android/app/src/main/res/drawable-xxxhdpi/ic_stat_replay_10.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
@ -103,5 +103,9 @@
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -16,6 +16,7 @@ import 'package:pilipala/pages/search/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/router/app_pages.dart';
|
||||
import 'package:pilipala/pages/main/view.dart';
|
||||
import 'package:pilipala/services/service_locator.dart';
|
||||
import 'package:pilipala/utils/app_scheme.dart';
|
||||
import 'package:pilipala/utils/data.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
@ -28,6 +29,7 @@ void main() async {
|
||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown])
|
||||
.then((_) async {
|
||||
await GStrorage.init();
|
||||
await setupServiceLocator();
|
||||
runApp(const MyApp());
|
||||
// 小白条、导航栏沉浸
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
|
@ -12,6 +12,7 @@ import 'package:pilipala/common/widgets/stat/view.dart';
|
||||
import 'package:pilipala/models/video_detail_res.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/controller.dart';
|
||||
import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
|
||||
import 'package:pilipala/services/service_locator.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
@ -51,12 +52,14 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
_futureBuilderFuture = videoIntroController.queryVideoIntro();
|
||||
videoIntroController.videoDetail.listen((value) {
|
||||
videoDetail = value;
|
||||
videoPlayerServiceHandler.onVideoIntroChange(value);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
videoIntroController.onClose();
|
||||
videoPlayerServiceHandler.onVideoIntroDispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
||||
@ -14,6 +15,7 @@ import 'package:ns_danmaku/ns_danmaku.dart';
|
||||
import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||
import 'package:pilipala/services/service_locator.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
@ -526,12 +528,24 @@ class PlPlayerController {
|
||||
}),
|
||||
videoPlayerController!.stream.buffering.listen((event) {
|
||||
isBuffering.value = event;
|
||||
videoPlayerServiceHandler.onStatusChange(
|
||||
playerStatus.status.value, event);
|
||||
}),
|
||||
// videoPlayerController!.stream.volume.listen((event) {
|
||||
// if (!mute.value && _volumeBeforeMute != event) {
|
||||
// _volumeBeforeMute = event / 100;
|
||||
// }
|
||||
// }),
|
||||
// 媒体通知监听
|
||||
onPlayerStatusChanged.listen((event) {
|
||||
videoPlayerServiceHandler.onStatusChange(event, isBuffering.value);
|
||||
}),
|
||||
onPositionChanged.listen((event) {
|
||||
EasyThrottle.throttle(
|
||||
'mediaServicePositon',
|
||||
const Duration(seconds: 1),
|
||||
() => videoPlayerServiceHandler.onPositionChange(event));
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
116
lib/services/audio_handler.dart
Normal file
116
lib/services/audio_handler.dart
Normal file
@ -0,0 +1,116 @@
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:pilipala/models/video_detail_res.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
|
||||
Future<VideoPlayerServiceHandler> initAudioService() async {
|
||||
return await AudioService.init(
|
||||
builder: () => VideoPlayerServiceHandler(),
|
||||
config: const AudioServiceConfig(
|
||||
androidNotificationChannelId: 'com.guozhigq.pilipala.audio',
|
||||
androidNotificationChannelName: 'Audio Service Pilipala',
|
||||
androidNotificationOngoing: true,
|
||||
androidStopForegroundOnPause: true,
|
||||
fastForwardInterval: Duration(seconds: 10),
|
||||
rewindInterval: Duration(seconds: 10),
|
||||
androidNotificationChannelDescription: 'Media notification channel',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class VideoPlayerServiceHandler extends BaseAudioHandler
|
||||
with QueueHandler, SeekHandler {
|
||||
static final List<MediaItem> _item = [];
|
||||
|
||||
@override
|
||||
Future<void> play() async {
|
||||
PlPlayerController.getInstance().play();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pause() async {
|
||||
PlPlayerController.getInstance().pause();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> seek(Duration position) async {
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
updatePosition: position,
|
||||
));
|
||||
await PlPlayerController.getInstance().seekTo(position);
|
||||
}
|
||||
|
||||
Future<void> setMediaItem(MediaItem newMediaItem) async {
|
||||
mediaItem.add(newMediaItem);
|
||||
addQueueItem(newMediaItem);
|
||||
}
|
||||
|
||||
Future<void> setPlaybackState(PlayerStatus status, bool isBuffering) async {
|
||||
final AudioProcessingState processingState;
|
||||
final playing = status == PlayerStatus.playing;
|
||||
if (status == PlayerStatus.completed) {
|
||||
processingState = AudioProcessingState.completed;
|
||||
} else if (isBuffering) {
|
||||
processingState = AudioProcessingState.buffering;
|
||||
} else {
|
||||
processingState = AudioProcessingState.ready;
|
||||
}
|
||||
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
processingState:
|
||||
isBuffering ? AudioProcessingState.buffering : processingState,
|
||||
controls: [
|
||||
MediaControl.rewind.copyWith(androidIcon: 'drawable/ic_stat_replay_10'),
|
||||
if (playing) MediaControl.pause else MediaControl.play,
|
||||
MediaControl.fastForward
|
||||
.copyWith(androidIcon: 'drawable/ic_stat_forward_10'),
|
||||
],
|
||||
playing: playing,
|
||||
systemActions: const {
|
||||
MediaAction.seek,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
onStatusChange(PlayerStatus status, bool isBuffering) {
|
||||
if (_item.isEmpty) return;
|
||||
setPlaybackState(status, isBuffering);
|
||||
}
|
||||
|
||||
onVideoIntroChange(VideoDetailData data) {
|
||||
Map argMap = Get.arguments;
|
||||
final heroTag = argMap['heroTag'];
|
||||
|
||||
final mediaItem = MediaItem(
|
||||
id: heroTag,
|
||||
title: data.title ?? "",
|
||||
artist: data.owner?.name ?? "",
|
||||
duration: Duration(seconds: data.duration ?? 0),
|
||||
artUri: Uri.parse(data.pic ?? ""),
|
||||
);
|
||||
setMediaItem(mediaItem);
|
||||
_item.add(mediaItem);
|
||||
}
|
||||
|
||||
onVideoIntroDispose() {
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
processingState: AudioProcessingState.idle,
|
||||
playing: false,
|
||||
));
|
||||
stop();
|
||||
_item.removeLast();
|
||||
if (_item.isNotEmpty) {
|
||||
setMediaItem(_item.last);
|
||||
}
|
||||
if (_item.isEmpty) {
|
||||
playbackState
|
||||
.add(playbackState.value.copyWith(updatePosition: Duration.zero));
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChange(Duration position) {
|
||||
playbackState.add(playbackState.value.copyWith(
|
||||
updatePosition: position,
|
||||
));
|
||||
}
|
||||
}
|
8
lib/services/service_locator.dart
Normal file
8
lib/services/service_locator.dart
Normal file
@ -0,0 +1,8 @@
|
||||
import 'audio_handler.dart';
|
||||
|
||||
late VideoPlayerServiceHandler videoPlayerServiceHandler;
|
||||
|
||||
Future<void> setupServiceLocator() async {
|
||||
final audio = await initAudioService();
|
||||
videoPlayerServiceHandler = audio;
|
||||
}
|
@ -5,6 +5,8 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import audio_service
|
||||
import audio_session
|
||||
import connectivity_plus
|
||||
import device_info_plus
|
||||
import dynamic_color
|
||||
@ -20,6 +22,8 @@ import url_launcher_macos
|
||||
import wakelock_plus
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
|
||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||
|
76
pubspec.lock
76
pubspec.lock
@ -57,6 +57,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
audio_service:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: audio_service
|
||||
sha256: a4d989f1225ea9621898d60f23236dcbfc04876fa316086c23c5c4af075dbac4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.18.12"
|
||||
audio_service_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audio_service_platform_interface
|
||||
sha256: "8431a455dac9916cc9ee6f7da5620a666436345c906ad2ebb7fa41d18b3c1bf4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
audio_service_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audio_service_web
|
||||
sha256: "523e64ddc914c714d53eec2da85bba1074f08cf26c786d4efb322de510815ea7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
audio_session:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audio_session
|
||||
sha256: "8a2bc5e30520e18f3fb0e366793d78057fb64cd5287862c76af0c8771f2a52ad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.16"
|
||||
audio_video_progress_bar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -213,10 +245,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.1"
|
||||
version: "1.17.2"
|
||||
connectivity_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -373,18 +405,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: extended_image
|
||||
sha256: e77d18f956649ba6e5ecebd0cb68542120886336a75ee673788145bd4c3f0767
|
||||
sha256: b4d72a27851751cfadaf048936d42939db7cd66c08fdcfe651eeaa1179714ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.2"
|
||||
version: "8.1.1"
|
||||
extended_image_library:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: extended_image_library
|
||||
sha256: bb8d08c504ebc73d476ec1c99451a61f12e95538869e734fc4f55a3a2d5c98ec
|
||||
sha256: "8bf87c0b14dcb59200c923a9a3952304e4732a0901e40811428834ef39018ee1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.5.3"
|
||||
version: "3.6.0"
|
||||
extended_list:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -665,10 +697,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6
|
||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.18.0"
|
||||
version: "0.18.1"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -737,18 +769,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.15"
|
||||
version: "0.12.16"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
||||
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.5.0"
|
||||
media_kit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1191,10 +1223,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.10.0"
|
||||
sqflite:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1279,10 +1311,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "0.6.0"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1475,6 +1507,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.4-beta"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1564,5 +1604,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
dart: ">=3.1.0-185.0.dev <4.0.0"
|
||||
flutter: ">=3.13.0"
|
||||
|
@ -49,7 +49,7 @@ dependencies:
|
||||
|
||||
# 图片
|
||||
cached_network_image: ^3.3.0
|
||||
extended_image: ^8.0.2
|
||||
extended_image: ^8.1.1
|
||||
saver_gallery: ^2.0.1
|
||||
|
||||
# 存储
|
||||
@ -86,7 +86,10 @@ dependencies:
|
||||
# 视频播放器
|
||||
media_kit: ^1.1.10 # Primary package.
|
||||
media_kit_video: ^1.2.4 # For video rendering.
|
||||
media_kit_libs_video: ^1.0.4
|
||||
media_kit_libs_video: ^1.0.4
|
||||
|
||||
# 媒体通知
|
||||
audio_service: ^0.18.12
|
||||
|
||||
# 音量、亮度、屏幕控制
|
||||
flutter_volume_controller: ^1.3.0
|
||||
|
Reference in New Issue
Block a user