|
|
|
@ -42,6 +42,9 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
.makeConnectable() |
|
|
|
.autoconnect() |
|
|
|
|
|
|
|
let bitrateChanged = NotificationCenter.default |
|
|
|
.publisher(for: NSNotification.Name.AVPlayerItemNewAccessLogEntry) |
|
|
|
|
|
|
|
let steps: [Float] = [0.25, 0.5, 1.0, 2.0] |
|
|
|
|
|
|
|
init(completionHandler: ((Bool) -> ())?, model: SVideoModel) { |
|
|
|
@ -117,7 +120,7 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
|
|
|
|
|
|
|
|
Text(model.currentSnapshot.name).foregroundColor(Color.blue) |
|
|
|
Text(" (\(model.height),\(model.nominalFrameRate)").foregroundColor(Color.blue) |
|
|
|
Text("(\(model.codec) \(model.height), \(model.nominalFrameRate), \(model.bitRate)m)").foregroundColor(Color.blue) |
|
|
|
|
|
|
|
ScrollView(.horizontal, showsIndicators: false) { |
|
|
|
HStack { |
|
|
|
@ -127,6 +130,7 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
}) { |
|
|
|
AsyncImage(item: item, placeholder: { Text("Loading ...") }, |
|
|
|
image: { Image(uiImage: $0).resizable() }) |
|
|
|
.overlay(Image(systemName: "repeat.circle").offset(x: 20, y: -20).opacity((item.length > 0.0) ? 1 : 0)) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -384,6 +388,14 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
.onReceive(orientationChanged) { _ in |
|
|
|
self.orientation = UIDevice.current.orientation |
|
|
|
} |
|
|
|
.onReceive(bitrateChanged) { notification in |
|
|
|
guard let playerItem = notification.object as? AVPlayerItem, |
|
|
|
let lastEvent = playerItem.accessLog()?.events.last else { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
model.bitRate = Int(lastEvent.indicatedBitrate / 1024 / 1024) |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
@ -571,7 +583,19 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
let heightSpace = model.proxy!.size.height * 2 |
|
|
|
var height = heightSpace |
|
|
|
if let i = player.currentItem { |
|
|
|
model.nominalFrameRate = await i.asset.load(.nominalFrameRate) |
|
|
|
Task.init { |
|
|
|
for t in i.asset.tracks { |
|
|
|
if t.mediaType == .video { |
|
|
|
model.nominalFrameRate = try await Int(t.load(.nominalFrameRate)) |
|
|
|
|
|
|
|
if let formats = t.formatDescriptions as? [CMFormatDescription] { |
|
|
|
for format in formats { |
|
|
|
printFourCC(CMFormatDescriptionGetMediaSubType(format)) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
height = i.presentationSize.height |
|
|
|
} |
|
|
|
model.height = Int(height) |
|
|
|
@ -595,6 +619,19 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
func printFourCC(_ fcc: FourCharCode) { |
|
|
|
let bytes: [CChar] = [ |
|
|
|
CChar((fcc >> 24) & 0xff), |
|
|
|
CChar((fcc >> 16) & 0xff), |
|
|
|
CChar((fcc >> 8) & 0xff), |
|
|
|
CChar(fcc & 0xff), |
|
|
|
0] |
|
|
|
|
|
|
|
let result = String(cString: bytes) |
|
|
|
let characterSet = CharacterSet.whitespaces |
|
|
|
model.codec = result.trimmingCharacters(in: characterSet) |
|
|
|
} |
|
|
|
|
|
|
|
func captureZoom() { |
|
|
|
model.currentSnapshot.scale = model.scale |
|
|
|
model.currentSnapshot.offset = CGPoint(x: model.dragOffset.width, y: model.dragOffset.height) |
|
|
|
|