Compare commits

...

3 Commits

Author SHA1 Message Date
1210cda998 Roll back to use media_kit as media player 2024-09-09 23:09:16 +08:00
3b56b94242 🚀 Launch 1.2.1+30 2024-09-09 13:07:21 +08:00
34043e722b Able to play music 2024-09-08 22:43:01 +08:00
8 changed files with 287 additions and 52 deletions

View File

@ -22,7 +22,8 @@
android:label="Solian"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true">
android:supportsRtl="true"
android:usesCleartextTraffic="true">
<receiver android:exported="false"
android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false"

View File

@ -1,4 +1,6 @@
PODS:
- audio_session (0.0.1):
- Flutter
- connectivity_plus (0.0.1):
- Flutter
- FlutterMacOS
@ -221,15 +223,11 @@ PODS:
- TOCropViewController (~> 2.7.4)
- image_picker_ios (0.0.1):
- Flutter
- just_audio (0.0.1):
- Flutter
- livekit_client (2.2.4):
- Flutter
- WebRTC-SDK (= 125.6422.04)
- media_kit_libs_ios_video (1.0.4):
- Flutter
- media_kit_native_event_loop (1.0.0):
- Flutter
- media_kit_video (0.0.1):
- Flutter
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
@ -251,8 +249,6 @@ PODS:
- PromisesObjC (= 2.4.0)
- protocol_handler_ios (0.0.1):
- Flutter
- screen_brightness_ios (0.1.0):
- Flutter
- SDWebImage (5.19.7):
- SDWebImage/Core (= 5.19.7)
- SDWebImage/Core (5.19.7)
@ -268,13 +264,15 @@ PODS:
- TOCropViewController (2.7.4)
- url_launcher_ios (0.0.1):
- Flutter
- volume_controller (0.0.1):
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- wakelock_plus (0.0.1):
- Flutter
- WebRTC-SDK (125.6422.04)
DEPENDENCIES:
- audio_session (from `.symlinks/plugins/audio_session/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
@ -291,22 +289,19 @@ DEPENDENCIES:
- gal (from `.symlinks/plugins/gal/darwin`)
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- just_audio (from `.symlinks/plugins/just_audio/ios`)
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
- protocol_handler_ios (from `.symlinks/plugins/protocol_handler_ios/ios`)
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
@ -339,6 +334,8 @@ SPEC REPOS:
- WebRTC-SDK
EXTERNAL SOURCES:
audio_session:
:path: ".symlinks/plugins/audio_session/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/darwin"
device_info_plus:
@ -371,14 +368,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_cropper/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
just_audio:
:path: ".symlinks/plugins/just_audio/ios"
livekit_client:
:path: ".symlinks/plugins/livekit_client/ios"
media_kit_libs_ios_video:
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
media_kit_native_event_loop:
:path: ".symlinks/plugins/media_kit_native_event_loop/ios"
media_kit_video:
:path: ".symlinks/plugins/media_kit_video/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
pasteboard:
@ -391,8 +384,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/pointer_interceptor_ios/ios"
protocol_handler_ios:
:path: ".symlinks/plugins/protocol_handler_ios/ios"
screen_brightness_ios:
:path: ".symlinks/plugins/screen_brightness_ios/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
@ -401,12 +392,13 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqflite/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
volume_controller:
:path: ".symlinks/plugins/volume_controller/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
audio_session: 088d2483ebd1dc43f51d253d4a1c517d9a2e7207
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
@ -442,10 +434,8 @@ SPEC CHECKSUMS:
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
livekit_client: d079c5f040d4bf2b80440ff0ae997725a183e4bc
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
pasteboard: 982969ebaa7c78af3e6cc7761e8f5e77565d9ce0
@ -455,7 +445,6 @@ SPEC CHECKSUMS:
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
protocol_handler_ios: a5db8abc38526ee326988b808be621e5fd568990
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
@ -463,7 +452,7 @@ SPEC CHECKSUMS:
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
WebRTC-SDK: c3d69a87e7185fad3568f6f3cff7c9ac5890acf3

View File

@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import 'package:media_kit/media_kit.dart';
import 'package:protocol_handler/protocol_handler.dart';
import 'package:provider/provider.dart';
import 'package:solian/bootstrapper.dart';
@ -35,7 +34,6 @@ import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized();
await Future.wait([
_initializeFirebase(),

View File

@ -0,0 +1,26 @@
extension DurationToHumanReadableString on Duration {
String toHumanReadableString({padZero = true}) {
final mm = inMinutes
.remainder(60)
.toString()
.padLeft(2, !padZero && inHours == 0 ? '' : '0');
final ss = inSeconds.remainder(60).toString().padLeft(2, '0');
if (inHours > 0) {
final hh = inHours.toString().padLeft(2, !padZero ? '' : '0');
return '$hh:$mm:$ss';
}
return '$mm:$ss';
}
}
extension ParseDuration on Duration {
static Duration fromString(String duration) {
final parts = duration.split(':').reversed.toList();
final seconds = int.parse(parts[0]);
final minutes = parts.length > 1 ? int.parse(parts[1]) : 0;
final hours = parts.length > 2 ? int.parse(parts[2]) : 0;
return Duration(hours: hours, minutes: minutes, seconds: seconds);
}
}

View File

@ -1,12 +1,16 @@
import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:solian/models/attachment.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/durations.dart';
import 'package:solian/services.dart';
import 'package:solian/widgets/sized_container.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -56,6 +60,11 @@ class _AttachmentItemState extends State<AttachmentItem> {
item: widget.item,
autoload: widget.autoload,
);
case 'audio':
return _AttachmentItemAudio(
item: widget.item,
autoload: widget.autoload,
);
default:
return Center(
child: Container(
@ -240,22 +249,21 @@ class _AttachmentItemVideo extends StatefulWidget {
}
class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
late final _player = Player(
configuration: const PlayerConfiguration(
logLevel: MPVLogLevel.error,
),
);
late final _controller = VideoController(_player);
bool _showContent = false;
Player? _videoPlayer;
VideoController? _videoController;
Future<void> _startLoad() async {
await _player.open(
Media(ServiceFinder.buildUrl('files', '/attachments/${widget.item.rid}')),
play: false,
);
setState(() => _showContent = true);
MediaKit.ensureInitialized();
final url = ServiceFinder.buildUrl(
'files',
'/attachments/${widget.item.rid}',
);
_videoPlayer = Player();
_videoController = VideoController(_videoPlayer!);
_videoPlayer!.open(Media(url), play: !widget.autoload);
}
@override
@ -305,17 +313,228 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
_startLoad();
},
);
} else if (_videoController == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Video(
controller: _videoController!,
aspectRatio: ratio,
controller: _controller,
);
}
@override
void dispose() {
_player.dispose();
_videoPlayer?.dispose();
super.dispose();
}
}
class _AttachmentItemAudio extends StatefulWidget {
final Attachment item;
final bool autoload;
const _AttachmentItemAudio({
required this.item,
this.autoload = false,
});
@override
State<_AttachmentItemAudio> createState() => _AttachmentItemAudioState();
}
class _AttachmentItemAudioState extends State<_AttachmentItemAudio> {
bool _showContent = false;
double? _draggingValue;
bool _isPlaying = false;
Duration _duration = Duration.zero;
Duration _position = Duration.zero;
Duration _bufferedPosition = Duration.zero;
Player? _audioPlayer;
Future<void> _startLoad() async {
setState(() => _showContent = true);
MediaKit.ensureInitialized();
final url = ServiceFinder.buildUrl(
'files',
'/attachments/${widget.item.rid}',
);
_audioPlayer = Player();
await _audioPlayer!.open(Media(url), play: !widget.autoload);
_audioPlayer!.stream.playing.listen((v) => setState(() => _isPlaying = v));
_audioPlayer!.stream.position.listen((v) => setState(() => _position = v));
_audioPlayer!.stream.duration.listen((v) => setState(() => _duration = v));
_audioPlayer!.stream.buffer.listen(
(v) => setState(() => _bufferedPosition = v),
);
}
@override
void initState() {
super.initState();
if (widget.autoload) {
_startLoad();
}
}
@override
Widget build(BuildContext context) {
const ratio = 16 / 9;
if (!_showContent) {
return GestureDetector(
child: AspectRatio(
aspectRatio: ratio,
child: CenteredContainer(
maxWidth: 280,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.not_started,
color: Colors.white,
size: 32,
),
const Gap(8),
Text(
'attachmentUnload'.tr,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
Text(
'attachmentUnloadCaption'.tr,
style: const TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
],
),
),
),
onTap: () {
_startLoad();
},
);
} else if (_audioPlayer == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
return AspectRatio(
aspectRatio: ratio,
child: CenteredContainer(
maxWidth: 320,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.audio_file, size: 32),
const Gap(8),
Text(
widget.item.alt,
style: const TextStyle(fontSize: 13),
textAlign: TextAlign.center,
),
const Gap(12),
Row(
children: [
Expanded(
child: Column(
children: [
SliderTheme(
data: SliderThemeData(
trackHeight: 2,
trackShape: _PlayerProgressTrackShape(),
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 8,
),
overlayShape: SliderComponentShape.noOverlay,
),
child: Slider(
secondaryTrackValue:
_bufferedPosition.inMilliseconds.abs().toDouble(),
value: _draggingValue?.abs() ??
_position.inMilliseconds.toDouble().abs(),
min: 0,
max: max(
_bufferedPosition.inMilliseconds.abs(),
max(
_position.inMilliseconds.abs(),
_duration.inMilliseconds.abs(),
),
).toDouble(),
onChanged: (value) {
setState(() => _draggingValue = value);
},
onChangeEnd: (value) {
_audioPlayer!
.seek(Duration(milliseconds: value.toInt()));
setState(() => _draggingValue = null);
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_position.toHumanReadableString(),
style: GoogleFonts.robotoMono(fontSize: 12),
),
Text(
_duration.toHumanReadableString(),
style: GoogleFonts.robotoMono(fontSize: 12),
),
],
).paddingSymmetric(horizontal: 8, vertical: 4),
],
),
),
const Gap(16),
IconButton.filled(
icon: _isPlaying
? const Icon(Icons.pause)
: const Icon(Icons.play_arrow),
onPressed: () {
_audioPlayer!.playOrPause();
},
visualDensity: const VisualDensity(
horizontal: -4,
vertical: 0,
),
),
],
),
],
),
),
);
}
@override
void dispose() {
_audioPlayer?.dispose();
super.dispose();
}
}
class _PlayerProgressTrackShape extends RoundedRectSliderTrackShape {
@override
Rect getPreferredRect({
required RenderBox parentBox,
Offset offset = Offset.zero,
required SliderThemeData sliderTheme,
bool isEnabled = false,
bool isDiscrete = false,
}) {
final trackHeight = sliderTheme.trackHeight;
final trackLeft = offset.dx;
final trackTop = offset.dy + (parentBox.size.height - trackHeight!) / 2;
final trackWidth = parentBox.size.width;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
}

View File

@ -94,6 +94,8 @@ class _AttachmentListState extends State<AttachmentList> {
} else {
portrait++;
}
} else if (entry.mimetype.split('/').firstOrNull == 'audio') {
landscape++;
}
}
if (isConsistent && consistentValue != null) {

View File

@ -362,10 +362,10 @@ packages:
dependency: "direct main"
description:
name: dio
sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0"
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
url: "https://pub.dev"
source: hosted
version: "5.6.0"
version: "5.7.0"
dio_web_adapter:
dependency: transitive
description:

View File

@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App"
publish_to: "none"
version: 1.2.1+28
version: 1.2.1+30
environment:
sdk: ">=3.3.4 <4.0.0"
@ -68,9 +68,6 @@ dependencies:
firebase_crashlytics: ^4.0.4
firebase_analytics: ^11.2.1
firebase_performance: ^0.10.0+4
media_kit: ^1.1.10+1
media_kit_video: ^1.2.4
media_kit_libs_video: ^1.0.4
flutter_svg: ^2.0.10+1
cross_file: ^0.3.4+2
google_fonts: ^6.2.1
@ -78,6 +75,9 @@ dependencies:
json_annotation: ^4.9.0
gap: ^3.0.1
fl_chart: ^0.69.0
media_kit: ^1.1.11
media_kit_video: ^1.2.5
media_kit_libs_video: ^1.0.5
dev_dependencies:
flutter_test: