- Remove Combine import and cancellables - Use NSKeyValueObservation for timeControlStatus (controlled teardown) - Invalidate itemStatusObserver before replacing playerItem - Store endObserver token for proper removal - Add cleanup() method called on view disappear - Proper deinit with direct property cleanup (nonisolated-safe)
106 lines
3.6 KiB
Swift
106 lines
3.6 KiB
Swift
import SwiftUI
|
||
|
||
// MARK: - MiniPlayer 三色图标(播放键 + 三色圆环)
|
||
struct MiniPlayerIcon: View {
|
||
var body: some View {
|
||
GeometryReader { geo in
|
||
let size = min(geo.size.width, geo.size.height)
|
||
ZStack {
|
||
// 三色圆环
|
||
RingArc(startAngle: -30, endAngle: 90)
|
||
.stroke(Color(red: 1.0, green: 0.23, blue: 0.19), lineWidth: size * 0.07)
|
||
.frame(width: size * 0.82, height: size * 0.82)
|
||
|
||
RingArc(startAngle: 90, endAngle: 210)
|
||
.stroke(Color(red: 0.20, green: 0.78, blue: 0.35), lineWidth: size * 0.07)
|
||
.frame(width: size * 0.82, height: size * 0.82)
|
||
|
||
RingArc(startAngle: 210, endAngle: 330)
|
||
.stroke(Color(red: 0.0, green: 0.48, blue: 1.0), lineWidth: size * 0.07)
|
||
.frame(width: size * 0.82, height: size * 0.82)
|
||
|
||
// 三色播放三角
|
||
TriColorPlay(size: size * 0.38)
|
||
.offset(x: size * 0.03) // 视觉居中偏移
|
||
}
|
||
.frame(width: size, height: size)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 圆弧段
|
||
struct RingArc: Shape {
|
||
let startAngle: Double
|
||
let endAngle: Double
|
||
|
||
func path(in rect: CGRect) -> Path {
|
||
var p = Path()
|
||
let center = CGPoint(x: rect.midX, y: rect.midY)
|
||
let radius = min(rect.width, rect.height) / 2
|
||
p.addArc(
|
||
center: center,
|
||
radius: radius,
|
||
startAngle: .degrees(startAngle),
|
||
endAngle: .degrees(endAngle),
|
||
clockwise: false
|
||
)
|
||
return p
|
||
}
|
||
}
|
||
|
||
// MARK: - 三色播放三角(中心到三顶点分割)
|
||
struct TriColorPlay: View {
|
||
let size: CGFloat
|
||
|
||
var body: some View {
|
||
Canvas { ctx, canvasSize in
|
||
let w = canvasSize.width
|
||
let h = canvasSize.height
|
||
|
||
// 三角顶点(播放键朝右)
|
||
let top = CGPoint(x: w * 0.15, y: 0)
|
||
let bottom = CGPoint(x: w * 0.15, y: h)
|
||
let right = CGPoint(x: w, y: h * 0.5)
|
||
|
||
// 重心
|
||
let cx = (top.x + bottom.x + right.x) / 3
|
||
let cy = (top.y + bottom.y + right.y) / 3
|
||
let center = CGPoint(x: cx, y: cy)
|
||
|
||
// 三色分区
|
||
let red = Path { p in
|
||
p.move(to: center)
|
||
p.addLine(to: top)
|
||
p.addLine(to: right)
|
||
p.closeSubpath()
|
||
}
|
||
let green = Path { p in
|
||
p.move(to: center)
|
||
p.addLine(to: right)
|
||
p.addLine(to: bottom)
|
||
p.closeSubpath()
|
||
}
|
||
let blue = Path { p in
|
||
p.move(to: center)
|
||
p.addLine(to: bottom)
|
||
p.addLine(to: top)
|
||
p.closeSubpath()
|
||
}
|
||
|
||
ctx.fill(red, with: .color(Color(red: 1.0, green: 0.23, blue: 0.19)))
|
||
ctx.fill(green, with: .color(Color(red: 0.20, green: 0.78, blue: 0.35)))
|
||
ctx.fill(blue, with: .color(Color(red: 0.0, green: 0.48, blue: 1.0)))
|
||
|
||
// 分割线
|
||
let lineW = max(0.5, w * 0.015)
|
||
for vertex in [top, bottom, right] {
|
||
var lp = Path()
|
||
lp.move(to: center)
|
||
lp.addLine(to: vertex)
|
||
ctx.stroke(lp, with: .color(.white.opacity(0.6)), lineWidth: lineW)
|
||
}
|
||
}
|
||
.frame(width: size, height: size)
|
||
}
|
||
}
|