Skip to content

Commit

Permalink
Merge pull request #7 from nini22P/dev
Browse files Browse the repository at this point in the history
v1.2.0
  • Loading branch information
nini22P authored Jan 25, 2025
2 parents 7623249 + 397f648 commit 5ac73fa
Show file tree
Hide file tree
Showing 53 changed files with 5,067 additions and 2,389 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
## v1.2.0

### Changelog
* Support jumping to video playback from external clicks (Windows version can play by command line or dragging files to the window)
* Support adjusting brightness and volume gestures (Brightness gestures are not available on Windows version)
* Support playing online links
* Add an option to always start playback from the beginning
* On Android 11 and above, file reading is changed to using the "Manage All Files" permission
* Improved WebDAV connection test function
* Improved some visual effects

### 更新日志
* 支持从外部点击视频跳转播放(Windows 版本可以通过命令行或者拖拽文件到窗口播放)
* 支持调整亮度和音量手势(Windows 版本调整亮度手势不可用)
* 支持播放在线链接
* 添加总是从头开始播放的选项
* Android 11 以上读取文件时改为使用 `管理所有文件` 权限
* 改进 WebDAV 测试连接功能
* 改进了部分视觉效果


## v1.1.1

### Changelog
Expand Down
14 changes: 12 additions & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<application
android:label="Iris"
android:name="${applicationName}"
Expand All @@ -10,7 +14,7 @@
android:fullBackupContent="false"
android:enableOnBackInvokedCallback="true"
>
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleInstance" android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" 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
Expand All @@ -20,6 +24,12 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="video/*" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
Expand Down
8 changes: 8 additions & 0 deletions lib/globals.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// ignore: unnecessary_library_name
library my_app.globals;

import 'package:permission_handler/permission_handler.dart';

List<String> arguments = [];
String? initUri;
PermissionStatus? storagePermissionStatus;
45 changes: 45 additions & 0 deletions lib/hooks/use_brightness.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:iris/utils/logger.dart';
import 'package:screen_brightness/screen_brightness.dart';

ValueNotifier<double?> useBrightness(bool isGesture) {
final brightness = useState<double?>(null);

useEffect(() {
try {
() async {
if (!isGesture) return;
brightness.value = await ScreenBrightness().current;
}();
} catch (e) {
logger('Error getting brightness: $e');
}
return () => brightness.value = null;
}, [isGesture]);

useEffect(() {
try {
if (brightness.value != null && isGesture) {
ScreenBrightness().setScreenBrightness(brightness.value!);
}
} catch (e) {
logger('Error setting brightness: $e');
}
return;
}, [brightness.value]);

// 退出时重置亮度
useEffect(
() => () {
try {
ScreenBrightness().resetScreenBrightness();
} catch (e) {
logger('Error resetting brightness: $e');
}
},
[],
);

return brightness;
}
10 changes: 7 additions & 3 deletions lib/hooks/use_player_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,15 @@ PlayerController usePlayerController(
Future<void> updateRate(double value) async =>
playerCore.rate == value ? null : await playerCore.player.setRate(value);

Future<void> shufflePlayQueue() async => usePlayQueueStore()
.update(getShufflePlayQueue(playQueue, currentIndex), currentIndex);
Future<void> shufflePlayQueue() async => usePlayQueueStore().update(
playQueue: getShufflePlayQueue(playQueue, currentIndex),
index: currentIndex,
);

Future<void> sortPlayQueue() async => usePlayQueueStore().update(
[...playQueue]..sort((a, b) => a.index.compareTo(b.index)), currentIndex);
playQueue: [...playQueue]..sort((a, b) => a.index.compareTo(b.index)),
index: currentIndex,
);

return PlayerController(
play,
Expand Down
60 changes: 38 additions & 22 deletions lib/hooks/use_player_core.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_zustand/flutter_zustand.dart';
Expand All @@ -12,8 +12,14 @@ import 'package:iris/store/use_history_store.dart';
import 'package:iris/store/use_play_queue_store.dart';
import 'package:iris/store/use_storage_store.dart';
import 'package:iris/utils/files_filter.dart';
import 'package:iris/utils/logger.dart';
import 'package:media_kit/media_kit.dart';

enum MediaType {
video,
audio,
}

class PlayerCore {
final Player player;
final SubtitleTrack subtitle;
Expand All @@ -24,13 +30,13 @@ class PlayerCore {
final bool playing;
final VideoParams? videoParams;
final AudioParams? audioParams;
final MediaType? mediaType;
final Duration position;
final Duration duration;
final Duration buffer;
final bool seeking;
final bool completed;
final double rate;
final double aspectRatio;
final FileItem? cover;
final void Function(Duration) updatePosition;
final void Function(bool) updateSeeking;
Expand All @@ -46,13 +52,13 @@ class PlayerCore {
this.playing,
this.videoParams,
this.audioParams,
this.mediaType,
this.position,
this.duration,
this.buffer,
this.seeking,
this.completed,
this.rate,
this.aspectRatio,
this.cover,
this.updatePosition,
this.updateSeeking,
Expand All @@ -72,6 +78,8 @@ PlayerCore usePlayerCore(BuildContext context, Player player) {
final bool autoPlay =
useAppStore().select(context, (state) => state.autoPlay);
final Repeat repeat = useAppStore().select(context, (state) => state.repeat);
final bool alwaysPlayFromBeginning =
useAppStore().select(context, (state) => state.alwaysPlayFromBeginning);

final history = useHistoryStore().select(context, (state) => state.history);

Expand Down Expand Up @@ -115,10 +123,9 @@ PlayerCore usePlayerCore(BuildContext context, Player player) {
(subtitle) => subtitles.any((item) => item.title == subtitle.name)),
[currentFile?.subtitles, subtitles]);

double aspectRatio =
videoParams != null && videoParams.w != null && videoParams.h != null
? (videoParams.w! / videoParams.h!)
: 0;
final MediaType? mediaType = useMemoized(
() => videoParams?.aspect != null ? MediaType.video : MediaType.audio,
[videoParams?.aspect]);

final positionStream = useStream(player.stream.position);

Expand All @@ -129,7 +136,9 @@ PlayerCore usePlayerCore(BuildContext context, Player player) {
}

final List<String> dir = useMemoized(
() => (currentFile == null) ? [] : ([...currentFile.path]..removeLast()),
() => currentFile == null || currentFile.path.isEmpty
? []
: ([...currentFile.path]..removeLast()),
[currentFile],
);

Expand Down Expand Up @@ -160,7 +169,7 @@ PlayerCore usePlayerCore(BuildContext context, Player player) {
if (currentFile == null || playQueue.isEmpty) {
player.stop();
} else {
log('Now playing: ${currentFile.name}, auto play: $autoPlay');
logger('Now playing: ${currentFile.uri}, auto play: $autoPlay');
player.open(
Media(currentFile.uri,
httpHeaders: currentFile.auth != null
Expand All @@ -170,8 +179,11 @@ PlayerCore usePlayerCore(BuildContext context, Player player) {
);
}
return () {
if (currentFile != null && player.state.duration == Duration.zero) {
log('Save progress: ${currentFile.name}');
if (currentFile != null && player.state.duration != Duration.zero) {
if (Platform.isAndroid && currentFile.uri.startsWith('content://')) {
return;
}
logger('Save progress: ${currentFile.name}');
useHistoryStore().add(Progress(
dateTime: DateTime.now().toUtc(),
position: player.state.position,
Expand All @@ -192,26 +204,29 @@ PlayerCore usePlayerCore(BuildContext context, Player player) {
if (currentFile != null && currentFile.type == ContentType.video) {
Progress? progress = history[currentFile.getID()];
if (progress != null) {
if (progress.duration.inMilliseconds == duration.inMilliseconds &&
if (!alwaysPlayFromBeginning &&
progress.duration.inMilliseconds == duration.inMilliseconds &&
(progress.duration.inMilliseconds -
progress.position.inMilliseconds) >
5000) {
log('Resume progress: ${currentFile.name} position: ${progress.position} duration: ${progress.duration}');
logger(
'Resume progress: ${currentFile.name} position: ${progress.position} duration: ${progress.duration}');
await player.seek(progress.position);
}
}
}
// 设置字幕
if (externalSubtitles!.isNotEmpty) {
log('Set external subtitle: ${externalSubtitles[0].name}');
logger('Set external subtitle: ${externalSubtitles[0].name}');
await player.setSubtitleTrack(
SubtitleTrack.uri(
externalSubtitles[0].uri,
title: externalSubtitles[0].name,
),
);
} else if (subtitles.length > 1) {
log('Set subtitle: ${subtitles[1].title ?? subtitles[1].language ?? subtitles[1].id}');
logger(
'Set subtitle: ${subtitles[1].title ?? subtitles[1].language ?? subtitles[1].id}');
await player.setSubtitleTrack(subtitles[1]);
} else {
await player.setSubtitleTrack(SubtitleTrack.no());
Expand All @@ -225,12 +240,10 @@ PlayerCore usePlayerCore(BuildContext context, Player player) {
if (completed) {
if (repeat == Repeat.one) return;
if (currentPlayIndex == playQueue.length - 1) {
if (repeat == Repeat.none) {
useAppStore().updateAutoPlay(false);
if (repeat == Repeat.all) {
await usePlayQueueStore().updateCurrentIndex(playQueue[0].index);
}
usePlayQueueStore().updateCurrentIndex(playQueue[0].index);
} else {
if (currentPlayIndex == playQueue.length - 1) return;
await usePlayQueueStore()
.updateCurrentIndex(playQueue[currentPlayIndex + 1].index);
}
Expand All @@ -240,7 +253,7 @@ PlayerCore usePlayerCore(BuildContext context, Player player) {
}, [completed, repeat]);

useEffect(() {
log('$repeat');
logger('$repeat');
if (repeat == Repeat.one) {
player.setPlaylistMode(PlaylistMode.loop);
} else {
Expand All @@ -255,7 +268,10 @@ PlayerCore usePlayerCore(BuildContext context, Player player) {

Future<void> saveProgress() async {
if (currentFile != null && player.state.duration != Duration.zero) {
log('Save progress: ${currentFile.name}');
if (Platform.isAndroid && currentFile.uri.startsWith('content://')) {
return;
}
logger('Save progress: ${currentFile.name}');
useHistoryStore().add(Progress(
dateTime: DateTime.now().toUtc(),
position: player.state.position,
Expand All @@ -275,13 +291,13 @@ PlayerCore usePlayerCore(BuildContext context, Player player) {
playing,
videoParams,
audioParams,
mediaType,
duration == Duration.zero ? Duration.zero : position.value,
duration,
duration == Duration.zero ? Duration.zero : buffer,
seeking.value,
completed,
rate,
aspectRatio,
cover,
updatePosition,
updateSeeking,
Expand Down
34 changes: 34 additions & 0 deletions lib/hooks/use_volume.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:iris/utils/logger.dart';

ValueNotifier<double?> useVolume(bool isGesture) {
final volume = useState<double?>(null);

useEffect(() {
try {
() async {
if (!isGesture) return;
await FlutterVolumeController.updateShowSystemUI(false);
volume.value = await FlutterVolumeController.getVolume();
}();
} catch (e) {
logger('Error getting volume: $e');
}
return () => volume.value = null;
}, [isGesture]);

useEffect(() {
try {
if (volume.value != null && isGesture) {
FlutterVolumeController.setVolume(volume.value!);
}
} catch (e) {
logger('Error setting volume: $e');
}
return;
}, [volume.value]);

return volume;
}
5 changes: 4 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"add_webdav_storage": "Add WebDAV storage",
"always_on_top_off": "Always on top: Off",
"always_on_top_on": "Always on top: On",
"always_play_from_beginning": "Always play from the beginning",
"always_play_from_beginning_description": "When enabled, this option will automatically start the video from the beginning each time it is played",
"app_description": "A lightweight video player",
"audio_track": "Audio track",
"author": "Author",
Expand All @@ -20,7 +22,6 @@
"checked_new_version": "Checked new version",
"close": "Close",
"confirmUpdate": "Confirm update",
"connection_test": "Connection test",
"crop": "Crop",
"dark": "Datk",
"download": "Download",
Expand All @@ -38,6 +39,7 @@
"grant_storage_permission": "Grant Storage Permission",
"history": "History",
"home": "Home",
"host": "Host",
"language": "Language",
"libraries": "Libraries",
"light": "Light",
Expand Down Expand Up @@ -78,6 +80,7 @@
"subtitle": "Subtitle",
"subtitle_and_audio_track": "Subtitle and audio track",
"system": "System",
"test_connection": "Test connection",
"theme_mode": "Theme mode",
"unable_to_fetch_files": "Unable to fetch files.",
"url": "URL",
Expand Down
Loading

0 comments on commit 5ac73fa

Please sign in to comment.