diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 39715280d..46ff2eefe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -239,6 +239,47 @@ jobs: name: fladder-windows-installer path: windows\Output\fladder_setup.exe + build-windows-arm64: + runs-on: windows-11-arm + needs: [fetch-info] + + steps: + - name: Checkout repository + uses: actions/checkout@v4.1.1 + + - name: Set up Flutter + uses: subosito/flutter-action@v2.19.0 + with: + channel: "master" + flutter-version: '010cd4f383' # Newer version may having breaking change with page_transition / CupertinoPageTransitionsBuilder + cache: true + cache-key: "flutter-:os:-:channel:-:version:-:arch:-:hash:" + cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" + + - name: Get dependencies + run: flutter pub get + + - name: Build Windows ARM64 EXE + run: flutter build windows --build-number=${{ github.run_number }} + + - name: Compile Inno Setup installer for ARM64 + uses: Minionguyjpro/Inno-Setup-Action@v1.2.2 + with: + path: windows/windows_setup_arm64.iss + options: /O+ /DFLADDER_VERSION="${{ needs.fetch-info.outputs.version_name }}" + + - name: Archive Windows ARM64 portable artifact + uses: actions/upload-artifact@v4.0.0 + with: + name: fladder-windows-arm64-portable + path: build\windows\arm64\runner\Release\ + + - name: Archive Windows ARM64 installer artifact + uses: actions/upload-artifact@v4.0.0 + with: + name: fladder-windows-arm64-installer + path: windows\Output\fladder_setup_arm64.exe + build-ios: runs-on: macos-latest needs: [fetch-info] @@ -458,6 +499,7 @@ jobs: - fetch-info - build-android - build-windows + - build-windows-arm64 - build-ios - build-macos - build-linux @@ -582,6 +624,28 @@ jobs: - name: Rename Windows installer run: mv fladder-windows-installer/fladder_setup.exe Fladder-Windows-${{ steps.version.outputs.version }}-Setup.exe + - name: Download Windows ARM64 portable artifact + uses: actions/download-artifact@v4 + with: + name: fladder-windows-arm64-portable + path: fladder-windows-arm64-portable + + - name: Compress Windows ARM64 + run: | + cd fladder-windows-arm64-portable + zip -r ../Fladder-Windows-ARM64-${{ steps.version.outputs.version }}.zip . + + - name: Download Windows ARM64 installer artifact + uses: actions/download-artifact@v4 + with: + name: fladder-windows-arm64-installer + path: fladder-windows-arm64-installer + + - name: Rename Windows ARM64 installer + run: mv fladder-windows-arm64-installer/fladder_setup_arm64.exe Fladder-Windows-ARM64-${{ steps.version.outputs.version }}-Setup.exe + + + - name: Download Artifacts iOS uses: actions/download-artifact@v4 with: @@ -659,6 +723,8 @@ jobs: Fladder-Android-${{ steps.version.outputs.version }}.aab Fladder-Windows-${{ steps.version.outputs.version }}-Setup.exe Fladder-Windows-${{ steps.version.outputs.version }}.zip + Fladder-Windows-ARM64-${{ steps.version.outputs.version }}-Setup.exe + Fladder-Windows-ARM64-${{ steps.version.outputs.version }}.zip Fladder-iOS-${{ steps.version.outputs.version }}.ipa Fladder-macOS-${{ steps.version.outputs.version }}.dmg Fladder-Web-${{ steps.version.outputs.version }}.zip diff --git a/lib/models/playback/direct_playback_model.dart b/lib/models/playback/direct_playback_model.dart index 19edfbfea..7f052c952 100644 --- a/lib/models/playback/direct_playback_model.dart +++ b/lib/models/playback/direct_playback_model.dart @@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart'; import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; +import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as jellyfin; import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/chapters_model.dart'; import 'package:fladder/models/items/item_shared_models.dart'; @@ -33,8 +33,9 @@ class DirectPlaybackModel extends PlaybackModel { @override List get subStreams => [SubStreamModel.no(), ...mediaStreams?.subStreams ?? []]; - List get itemsInQueue => - queue.mapIndexed((index, element) => QueueItem(id: element.id, playlistItemId: "playlistItem$index")).toList(); + List get itemsInQueue => queue + .mapIndexed((index, element) => jellyfin.QueueItem(id: element.id, playlistItemId: "playlistItem$index")) + .toList(); @override Future setSubtitle(SubStreamModel? model, MediaControlsWrapper player) async { @@ -59,7 +60,7 @@ class DirectPlaybackModel extends PlaybackModel { @override Future playbackStarted(Duration position, Ref ref) async { await ref.read(jellyApiProvider).sessionsPlayingPost( - body: PlaybackStartInfo( + body: jellyfin.PlaybackStartInfo( canSeek: true, itemId: item.id, mediaSourceId: item.id, @@ -68,10 +69,10 @@ class DirectPlaybackModel extends PlaybackModel { audioStreamIndex: item.streamModel?.defaultAudioStreamIndex, volumeLevel: 100, playbackStartTimeTicks: position.toRuntimeTicks, - playMethod: PlayMethod.directplay, + playMethod: jellyfin.PlayMethod.directplay, isMuted: false, isPaused: false, - repeatMode: RepeatMode.repeatall, + repeatMode: jellyfin.RepeatMode.repeatall, ), ); return null; @@ -82,7 +83,7 @@ class DirectPlaybackModel extends PlaybackModel { ref.read(playBackModel.notifier).update((state) => null); await ref.read(jellyApiProvider).sessionsPlayingStoppedPost( - body: PlaybackStopInfo( + body: jellyfin.PlaybackStopInfo( itemId: item.id, mediaSourceId: item.id, playSessionId: playbackInfo?.playSessionId, @@ -97,7 +98,7 @@ class DirectPlaybackModel extends PlaybackModel { Future updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) async { final api = ref.read(jellyApiProvider); await api.sessionsPlayingProgressPost( - body: PlaybackProgressInfo( + body: jellyfin.PlaybackProgressInfo( canSeek: true, itemId: item.id, mediaSourceId: item.id, @@ -105,11 +106,11 @@ class DirectPlaybackModel extends PlaybackModel { subtitleStreamIndex: item.streamModel?.defaultSubStreamIndex, audioStreamIndex: item.streamModel?.defaultAudioStreamIndex, volumeLevel: 100, - playMethod: PlayMethod.directplay, + playMethod: jellyfin.PlayMethod.directplay, isPaused: !isPlaying, isMuted: false, positionTicks: position.toRuntimeTicks, - repeatMode: RepeatMode.repeatall, + repeatMode: jellyfin.RepeatMode.repeatall, ), ); @@ -133,7 +134,7 @@ class DirectPlaybackModel extends PlaybackModel { ItemBaseModel? item, ValueGetter? media, ValueGetter? lastPosition, - PlaybackInfoResponse? playbackInfo, + jellyfin.PlaybackInfoResponse? playbackInfo, ValueGetter? mediaStreams, ValueGetter? mediaSegments, ValueGetter?>? chapters, diff --git a/lib/models/playback/transcode_playback_model.dart b/lib/models/playback/transcode_playback_model.dart index 4b0110542..11e977b10 100644 --- a/lib/models/playback/transcode_playback_model.dart +++ b/lib/models/playback/transcode_playback_model.dart @@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart'; import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; +import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as jellyfin; import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/chapters_model.dart'; import 'package:fladder/models/items/item_shared_models.dart'; @@ -33,8 +33,9 @@ class TranscodePlaybackModel extends PlaybackModel { @override List get subStreams => [SubStreamModel.no(), ...mediaStreams?.subStreams ?? []]; - List get itemsInQueue => - queue.mapIndexed((index, element) => QueueItem(id: element.id, playlistItemId: "playlistItem$index")).toList(); + List get itemsInQueue => queue + .mapIndexed((index, element) => jellyfin.QueueItem(id: element.id, playlistItemId: "playlistItem$index")) + .toList(); @override Future setSubtitle(SubStreamModel? model, MediaControlsWrapper player) async { @@ -57,7 +58,7 @@ class TranscodePlaybackModel extends PlaybackModel { @override Future playbackStarted(Duration position, Ref ref) async { await ref.read(jellyApiProvider).sessionsPlayingPost( - body: PlaybackStartInfo( + body: jellyfin.PlaybackStartInfo( canSeek: true, itemId: item.id, mediaSourceId: item.id, @@ -67,10 +68,10 @@ class TranscodePlaybackModel extends PlaybackModel { audioStreamIndex: item.streamModel?.defaultAudioStreamIndex, volumeLevel: 100, playbackStartTimeTicks: position.toRuntimeTicks, - playMethod: PlayMethod.transcode, + playMethod: jellyfin.PlayMethod.transcode, isMuted: false, isPaused: false, - repeatMode: RepeatMode.repeatall, + repeatMode: jellyfin.RepeatMode.repeatall, ), ); return null; @@ -81,7 +82,7 @@ class TranscodePlaybackModel extends PlaybackModel { ref.read(playBackModel.notifier).update((state) => null); await ref.read(jellyApiProvider).sessionsPlayingStoppedPost( - body: PlaybackStopInfo( + body: jellyfin.PlaybackStopInfo( itemId: item.id, mediaSourceId: item.id, playSessionId: playbackInfo?.playSessionId, @@ -96,7 +97,7 @@ class TranscodePlaybackModel extends PlaybackModel { Future updatePlaybackPosition(Duration position, bool isPlaying, Ref ref) async { final api = ref.read(jellyApiProvider); await api.sessionsPlayingProgressPost( - body: PlaybackProgressInfo( + body: jellyfin.PlaybackProgressInfo( canSeek: true, itemId: item.id, mediaSourceId: item.id, @@ -106,10 +107,10 @@ class TranscodePlaybackModel extends PlaybackModel { audioStreamIndex: item.streamModel?.defaultAudioStreamIndex, volumeLevel: 100, positionTicks: position.toRuntimeTicks, - playMethod: PlayMethod.transcode, + playMethod: jellyfin.PlayMethod.transcode, isPaused: !isPlaying, isMuted: false, - repeatMode: RepeatMode.repeatall, + repeatMode: jellyfin.RepeatMode.repeatall, ), ); return this; @@ -132,7 +133,7 @@ class TranscodePlaybackModel extends PlaybackModel { ItemBaseModel? item, ValueGetter? media, ValueGetter? lastPosition, - PlaybackInfoResponse? playbackInfo, + jellyfin.PlaybackInfoResponse? playbackInfo, ValueGetter? mediaStreams, ValueGetter? mediaSegments, ValueGetter?>? chapters, diff --git a/pubspec.yaml b/pubspec.yaml index 8ffccb2c7..459e1a0c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -173,8 +173,9 @@ dependency_overrides: path: libs/macos/media_kit_libs_macos_video media_kit_libs_windows_video: git: - url: https://github.com/DonutWare/media-kit.git + url: https://github.com/talynone/media-kit.git path: libs/windows/media_kit_libs_windows_video + ref: WINARM64 media_kit_libs_linux: git: url: https://github.com/DonutWare/media-kit.git diff --git a/windows/windows_setup_arm64.iss b/windows/windows_setup_arm64.iss new file mode 100644 index 000000000..9f9eb0a96 --- /dev/null +++ b/windows/windows_setup_arm64.iss @@ -0,0 +1,65 @@ +#define SourcePath ".." + +#ifndef FLADDER_VERSION + #define FLADDER_VERSION "latest" +#endif + +[Setup] +AppId={{D573EDD5-117A-47AD-88AC-62C8EBD11DC8} +AppName="Fladder" +AppVersion={#FLADDER_VERSION} +AppPublisher="DonutWare" +AppPublisherURL="https://github.com/DonutWare/Fladder" +AppSupportURL="https://github.com/DonutWare/Fladder" +AppUpdatesURL="https://github.com/DonutWare/Fladder" +DefaultDirName={localappdata}\Programs\Fladder +ArchitecturesAllowed=arm64 +ArchitecturesInstallIn64BitMode=arm64 +DisableProgramGroupPage=yes +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog +OutputBaseFilename=fladder_setup_arm64 +Compression=lzma +SolidCompression=yes +WizardStyle=modern + +SetupLogging=yes +UninstallLogging=yes +UninstallDisplayName="Fladder (ARM64)" +UninstallDisplayIcon={app}\fladder.exe +SetupIconFile="{#SourcePath}\icons\production\fladder_icon.ico" +LicenseFile="{#SourcePath}\LICENSE" +WizardImageFile={#SourcePath}\assets\windows-installer\fladder-installer-100.bmp,{#SourcePath}\assets\windows-installer\fladder-installer-125.bmp,{#SourcePath}\assets\windows-installer\fladder-installer-150.bmp + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "{#SourcePath}\build\windows\arm64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{autoprograms}\Fladder"; Filename: "{app}\fladder.exe" +Name: "{autodesktop}\Fladder"; Filename: "{app}\fladder.exe"; Tasks: desktopicon + +[Run] +Filename: "{app}\fladder.exe"; Description: "{cm:LaunchProgram,Fladder}"; Flags: nowait postinstall skipifsilent + +[Code] +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + case CurUninstallStep of + usUninstall: + begin + if MsgBox('Would you like to delete the application''s data? This action cannot be undone. Synced files will remain unaffected.', mbConfirmation, MB_YESNO) = IDYES then + begin + if DelTree(ExpandConstant('{localappdata}\DonutWare'), True, True, True) = False then + begin + Log(ExpandConstant('{localappdata}\DonutWare could not be deleted. Skipping...')); + end; + end; + end; + end; +end;