🐛 Fix bugs and don't know what has been fixed

This commit is contained in:
2025-07-11 00:44:08 +08:00
parent f2d2a9efd8
commit 6d06f0a1b4
12 changed files with 269 additions and 66 deletions

View File

@@ -45,10 +45,10 @@ PODS:
- Firebase/Messaging (11.15.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.15.0)
- firebase_core (3.15.0):
- firebase_core (3.15.1):
- Firebase/CoreOnly (= 11.15.0)
- Flutter
- firebase_messaging (15.2.8):
- firebase_messaging (15.2.9):
- Firebase/Messaging (= 11.15.0)
- firebase_core
- Flutter
@@ -130,7 +130,7 @@ PODS:
- Flutter
- irondash_engine_context (0.0.1):
- Flutter
- Kingfisher (8.3.3)
- Kingfisher (8.4.0)
- livekit_client (2.4.9):
- Flutter
- flutter_webrtc
@@ -178,18 +178,18 @@ PODS:
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- sqlite3 (3.50.1):
- sqlite3/common (= 3.50.1)
- sqlite3/common (3.50.1)
- sqlite3/dbstatvtab (3.50.1):
- sqlite3 (3.50.2):
- sqlite3/common (= 3.50.2)
- sqlite3/common (3.50.2)
- sqlite3/dbstatvtab (3.50.2):
- sqlite3/common
- sqlite3/fts5 (3.50.1):
- sqlite3/fts5 (3.50.2):
- sqlite3/common
- sqlite3/math (3.50.1):
- sqlite3/math (3.50.2):
- sqlite3/common
- sqlite3/perf-threadsafe (3.50.1):
- sqlite3/perf-threadsafe (3.50.2):
- sqlite3/common
- sqlite3/rtree (3.50.1):
- sqlite3/rtree (3.50.2):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
@@ -362,8 +362,8 @@ SPEC CHECKSUMS:
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
firebase_core: c727a02c560a53f1f1e56e18f16515eb5753c492
firebase_messaging: 4158969b04b667f5435731ec9d6e453bb58b0c4c
firebase_core: ece862f94b2bc72ee0edbeec7ab5c7cb09fe1ab5
firebase_messaging: e1a5fae495603115be1d0183bc849da748734e2b
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
@@ -382,9 +382,9 @@ SPEC CHECKSUMS:
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
Kingfisher: ff82cb91d9266ddb56cbb2f72d32c26f00d3e5be
Kingfisher: b14cc47bbfa7a3c150dd12962ee9c86338545629
livekit_client: 3f79d79233a5bd13d5b541732624ef959d7c538e
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
@@ -403,7 +403,7 @@ SPEC CHECKSUMS:
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: 1d85290c3321153511f6e900ede7a1608718bbd5
sqlite3: 3e82a2daae39ba3b41ae6ee84a130494585460fc
sqlite3_flutter_libs: e7fc8c9ea2200ff3271f08f127842131746b70e2
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4

View File

@@ -27,6 +27,7 @@ import UIKit
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
@@ -14,13 +16,14 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-26" y="-76"/>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,200 @@
import Flutter
import UIKit
import AVKit
// Factory to create the native video player view
class VideoPlayerViewFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(
withFrame frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?
) -> FlutterPlatformView {
return VideoPlayerView(
frame: frame,
viewIdentifier: viewId,
arguments: args,
binaryMessenger: messenger)
}
// This is required by the protocol, but we don't need to implement it for this case
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
}
// The actual PlatformView that holds the AVPlayerViewController
class VideoPlayerView: NSObject, FlutterPlatformView {
private var playerViewController: AVPlayerViewController?
private var player: AVPlayer?
private var activityIndicator: UIActivityIndicatorView!
private var progressLabel: UILabel!
private var progressStack: UIStackView!
// KVO contexts
private var playerStatusContext = 0
private var playerLoadedTimeRangesContext = 0
init(
frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?,
binaryMessenger messenger: FlutterBinaryMessenger?
) {
super.init()
// Ensure we have a valid URL from Flutter
guard let args = args as? [String: Any],
let videoUrlString = args["videoUrl"] as? String,
let videoUrl = URL(string: videoUrlString) else {
// Initialize playerViewController even if URL is invalid
playerViewController = AVPlayerViewController()
playerViewController!.showsPlaybackControls = false // Hide controls for invalid URL
let label = UILabel()
label.text = "Invalid video URL"
label.textAlignment = .center
label.textColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
playerViewController!.contentOverlayView?.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: playerViewController!.contentOverlayView!.centerXAnchor),
label.centerYAnchor.constraint(equalTo: playerViewController!.contentOverlayView!.centerYAnchor)
])
return
}
// --- Player ---
player = AVPlayer(url: videoUrl)
// --- PlayerViewController ---
playerViewController = AVPlayerViewController()
playerViewController!.player = player
playerViewController!.view.frame = frame // Set the frame directly
playerViewController!.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
playerViewController!.showsPlaybackControls = true
// --- Loading Indicator (spinner) ---
activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.color = .white
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.isUserInteractionEnabled = false // Allow touches to pass through
// --- Progress Percentage Label ---
progressLabel = UILabel()
progressLabel.textColor = .white
progressLabel.textAlignment = .center
progressLabel.translatesAutoresizingMaskIntoConstraints = false
progressLabel.isUserInteractionEnabled = false // Allow touches to pass through
playerViewController!.contentOverlayView?.addSubview(activityIndicator)
playerViewController!.contentOverlayView?.addSubview(progressLabel)
// Center the activity indicator and place the progress label below it
NSLayoutConstraint.activate([
activityIndicator.centerXAnchor.constraint(equalTo: playerViewController!.contentOverlayView!.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: playerViewController!.contentOverlayView!.centerYAnchor),
progressLabel.topAnchor.constraint(equalTo: activityIndicator.bottomAnchor, constant: 16),
progressLabel.centerXAnchor.constraint(equalTo: playerViewController!.contentOverlayView!.centerXAnchor),
])
// Add Key-Value Observers
addObservers()
activityIndicator.startAnimating()
}
func view() -> UIView {
return playerViewController!.view
}
private func addObservers() {
player?.addObserver(self, forKeyPath: #keyPath(AVPlayer.status), options: [.new, .initial], context: &playerStatusContext)
player?.currentItem?.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.loadedTimeRanges), options: [.new], context: &playerLoadedTimeRangesContext)
}
private func removeObservers() {
// Check if observers are registered before removing them to avoid crashes.
// A simple way is to use a flag or check the player object, but for KVO,
// it's often safer to just ensure they are added once and removed once.
// Given the lifecycle here, direct removal in deinit is okay.
player?.removeObserver(self, forKeyPath: #keyPath(AVPlayer.status), context: &playerStatusContext)
player?.currentItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.loadedTimeRanges), context: &playerLoadedTimeRangesContext)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// Dispatch to main queue to ensure thread safety
DispatchQueue.main.async {
guard self.player != nil else {
return
}
if context == &self.playerStatusContext {
self.handlePlayerStatusChange(change: change)
} else if context == &self.playerLoadedTimeRangesContext {
self.handleLoadedTimeRangesChange()
}
}
}
private func handlePlayerStatusChange(change: [NSKeyValueChangeKey : Any]?) {
guard let statusValue = change?[.newKey] as? Int, let status = AVPlayer.Status(rawValue: statusValue) else { return }
DispatchQueue.main.async {
switch status {
case .readyToPlay:
self.activityIndicator.stopAnimating()
self.progressLabel.isHidden = true
self.player?.play()
case .failed:
self.activityIndicator.stopAnimating()
// Optionally: show an error message to the user
let label = UILabel()
label.text = "Failed to load video"
label.textColor = .white
label.textAlignment = .center
label.frame = self.playerViewController!.view.bounds
self.playerViewController!.view.addSubview(label)
case .unknown:
self.activityIndicator.startAnimating()
@unknown default:
break
}
}
}
private func handleLoadedTimeRangesChange() {
guard let playerItem = player?.currentItem,
let timeRange = playerItem.loadedTimeRanges.first?.timeRangeValue,
!CMTIME_IS_INDEFINITE(playerItem.duration) else {
return
}
let startSeconds = CMTimeGetSeconds(timeRange.start)
let durationSeconds = CMTimeGetSeconds(timeRange.duration)
let totalBuffer = startSeconds + durationSeconds
let totalDuration = CMTimeGetSeconds(playerItem.duration)
let progress = Float(totalBuffer / totalDuration)
DispatchQueue.main.async {
self.progressLabel.text = "\(Int(progress * 100))%"
if progress >= 0.99 {
self.progressLabel.isHidden = true
}
}
}
deinit {
removeObservers()
}
}