feat(iOS): add recording, media library, and local file import

- Wire up HLSRecorder for iOS stream recording (toggleRecording)
- Add MediaLibraryView sheet for iOS (toggleLibraryWindow)
- Add .fileImporter for iOS local file playback (openFileDialog)
- Add Assets.xcassets with app icons
- Add UniformTypeIdentifiers import for iOS
- Add handleFileImport() with security-scoped resource access
This commit is contained in:
yumoqing 2026-07-01 22:55:35 +08:00
parent 4bfd4e5565
commit 44708f39ef
14 changed files with 161 additions and 2 deletions

View File

@ -0,0 +1,74 @@
{
"images": [
{
"filename": "icon_40.png",
"idiom": "iphone",
"scale": "2x",
"size": "20x20"
},
{
"filename": "icon_60.png",
"idiom": "iphone",
"scale": "3x",
"size": "20x20"
},
{
"filename": "icon_58.png",
"idiom": "iphone",
"scale": "2x",
"size": "29x29"
},
{
"filename": "icon_87.png",
"idiom": "iphone",
"scale": "3x",
"size": "29x29"
},
{
"filename": "icon_80.png",
"idiom": "iphone",
"scale": "2x",
"size": "40x40"
},
{
"filename": "icon_120.png",
"idiom": "iphone",
"scale": "3x",
"size": "40x40"
},
{
"filename": "icon_120.png",
"idiom": "iphone",
"scale": "2x",
"size": "60x60"
},
{
"filename": "icon_180.png",
"idiom": "iphone",
"scale": "3x",
"size": "60x60"
},
{
"filename": "icon_152.png",
"idiom": "ipad",
"scale": "2x",
"size": "76x76"
},
{
"filename": "icon_167.png",
"idiom": "ipad",
"scale": "2x",
"size": "83.5x83.5"
},
{
"filename": "icon_1024.png",
"idiom": "ios-marketing",
"scale": "1x",
"size": "1024x1024"
}
],
"info": {
"author": "MiniPlayer",
"version": 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -6,6 +6,7 @@ import SwiftBricks
import AppKit
#elseif os(iOS)
import UIKit
import UniformTypeIdentifiers
#endif
#if os(macOS)
@ -456,6 +457,23 @@ struct BricksAppView: View {
.sheet(isPresented: $bridge.showPlaylistSheet) {
PlaylistWindowView(bridge: bridge)
}
.sheet(isPresented: $bridge.showLibrarySheet) {
MediaLibraryView(library: bridge.library, bridge: bridge)
}
#if os(iOS)
.fileImporter(
isPresented: $bridge.showFileImporter,
allowedContentTypes: [.movie, .video, .audio, .mpeg4Movie, .quickTimeMovie, .mp3, .wav, .mpeg4Audio, .plainText, .item],
allowsMultipleSelection: true
) { result in
switch result {
case .success(let urls):
bridge.handleFileImport(urls)
case .failure(let error):
bridge.showToastMsg(error.localizedDescription)
}
}
#endif
}
}
@ -486,8 +504,8 @@ struct ControlToolbar: View {
//
HStack(spacing: 10) {
//
#if os(macOS)
//
if bridge.screenRecorder.isRecording {
HStack(spacing: 4) {
Image(systemName: "stop.circle.fill")
@ -504,9 +522,26 @@ struct ControlToolbar: View {
bridge.toggleRecording(); onTouch()
}
}
#elseif os(iOS)
if bridge.hlsRecorder.isRecording {
HStack(spacing: 4) {
Image(systemName: "stop.circle.fill")
.foregroundColor(.red)
.font(.system(size: 18))
Text(bridge.hlsRecorder.durationText)
.font(.system(size: 12, design: .monospaced))
.foregroundColor(.red)
}
.contentShape(Rectangle())
.onTapGesture { bridge.toggleRecording(); onTouch() }
} else {
TB(icon: "record.circle") {
bridge.toggleRecording(); onTouch()
}
}
#endif
Divider().frame(height: 20).padding(.horizontal, 4)
#endif
TB(icon: "folder") { bridge.openFileDialog(); onTouch() }
TB(icon: "link") { bridge.showURLDialog = true; onTouch() }

View File

@ -73,6 +73,8 @@ final class PlayerBridge: ObservableObject {
@Published var showURLDialog = false
@Published var showTrackDialog = false
@Published var showQRScanner = false
@Published var showLibrarySheet = false
@Published var showFileImporter = false
#if os(macOS)
var qrScannerController: QRScannerWindowController?
#endif
@ -115,6 +117,10 @@ final class PlayerBridge: ObservableObject {
let screenRecorder = PlayerRecorder()
#endif
#if os(iOS)
let hlsRecorder = HLSRecorder()
#endif
// MARK: -
func setup() {
#if os(macOS)
@ -135,6 +141,15 @@ final class PlayerBridge: ObservableObject {
self?.showToast(msg)
}
#endif
#if os(iOS)
hlsRecorder.onRecordingSaved = { [weak self] url in
self?.showToast("Saved: \(url.lastPathComponent)")
}
hlsRecorder.onError = { [weak self] msg in
self?.showToast(msg)
}
#endif
}
// MARK: -
@ -150,6 +165,17 @@ final class PlayerBridge: ObservableObject {
let item = queue[currentIndex]
screenRecorder.startRecording(url: item.url)
}
#elseif os(iOS)
if hlsRecorder.isRecording {
hlsRecorder.stopRecording()
} else {
guard !queue.isEmpty, currentIndex >= 0, currentIndex < queue.count else {
showToast("No media playing")
return
}
let item = queue[currentIndex]
hlsRecorder.startRecording(url: item.url)
}
#endif
}
@ -482,6 +508,8 @@ final class PlayerBridge: ObservableObject {
addItem(url: url, name: url.lastPathComponent, type: "file")
}
}
#elseif os(iOS)
showFileImporter = true
#endif
}
@ -645,9 +673,23 @@ final class PlayerBridge: ObservableObject {
}
showToastMsg("\(L.imported) \(count) \(L.localFilesAdded)")
}
#elseif os(iOS)
showFileImporter = true
#endif
}
/// iOS
func handleFileImport(_ urls: [URL]) {
for url in urls {
// iOS: startAccessingSecurityScopedResource 访
let didStartAccessing = url.startAccessingSecurityScopedResource()
defer {
if didStartAccessing { url.stopAccessingSecurityScopedResource() }
}
addItem(url: url, name: url.lastPathComponent, type: "file")
}
}
// MARK: -
func toggleLibraryWindow() {
@ -668,6 +710,8 @@ final class PlayerBridge: ObservableObject {
panel.contentView = NSHostingView(rootView: MediaLibraryView(library: library, bridge: self))
panel.makeKeyAndOrderFront(NSApp)
libraryWindow = panel
#elseif os(iOS)
showLibrarySheet.toggle()
#endif
}