1
0
mirror of git://jb55.com/damus synced 2024-09-30 00:40:45 +00:00

Add GSPlayer + VideoPlayer

This commit is contained in:
William Casarin 2023-05-26 10:00:29 -07:00
parent 185fba150f
commit 4d95d36a1e
3 changed files with 364 additions and 0 deletions

View File

@ -207,6 +207,8 @@
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; }; 4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAF9297F64AC00430951 /* EventMenu.swift */; };
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */; }; 4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */; };
4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */; }; 4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */; };
4CCF9AAF2A1FDBDB00E03CFB /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCF9AAE2A1FDBDB00E03CFB /* VideoPlayer.swift */; };
4CCF9AB22A1FE80C00E03CFB /* GSPlayer in Frameworks */ = {isa = PBXBuildFile; productRef = 4CCF9AB12A1FE80C00E03CFB /* GSPlayer */; };
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */; }; 4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */; };
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; }; 4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128929E9D10C0006FA5A /* SignalView.swift */; }; 4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA128929E9D10C0006FA5A /* SignalView.swift */; };
@ -651,6 +653,7 @@
4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; }; 4CC7AAF9297F64AC00430951 /* EventMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMenu.swift; sourceTree = "<group>"; };
4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingEventView.swift; sourceTree = "<group>"; }; 4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingEventView.swift; sourceTree = "<group>"; };
4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingProfileView.swift; sourceTree = "<group>"; }; 4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchingProfileView.swift; sourceTree = "<group>"; };
4CCF9AAE2A1FDBDB00E03CFB /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = "<group>"; };
4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadModel.swift; sourceTree = "<group>"; }; 4CD348EE29C3659D00497EB2 /* ImageUploadModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploadModel.swift; sourceTree = "<group>"; };
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; }; 4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
4CDA128929E9D10C0006FA5A /* SignalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalView.swift; sourceTree = "<group>"; }; 4CDA128929E9D10C0006FA5A /* SignalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalView.swift; sourceTree = "<group>"; };
@ -771,6 +774,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */, 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
4CCF9AB22A1FE80C00E03CFB /* GSPlayer in Frameworks */,
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */, 4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -949,6 +953,14 @@
path = Settings; path = Settings;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
4C1A9A2829DDF53B00516EAC /* Video */ = {
isa = PBXGroup;
children = (
4CCF9AAE2A1FDBDB00E03CFB /* VideoPlayer.swift */,
);
path = Video;
sourceTree = "<group>";
};
4C30AC7029A5676F00E2BD5A /* Notifications */ = { 4C30AC7029A5676F00E2BD5A /* Notifications */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -975,6 +987,7 @@
4C7D09692A0AEA0400943473 /* CodeScanner */, 4C7D09692A0AEA0400943473 /* CodeScanner */,
4C7D095A2A098C5C00943473 /* Wallet */, 4C7D095A2A098C5C00943473 /* Wallet */,
4C8D1A6D29F31E4100ACDF75 /* Buttons */, 4C8D1A6D29F31E4100ACDF75 /* Buttons */,
4C1A9A2829DDF53B00516EAC /* Video */,
4C1A9A1B29DDCF8B00516EAC /* Settings */, 4C1A9A1B29DDCF8B00516EAC /* Settings */,
4CFF8F6129CC9A80008DB934 /* Images */, 4CFF8F6129CC9A80008DB934 /* Images */,
4CCEB7AC29B53D180078AA28 /* Search */, 4CCEB7AC29B53D180078AA28 /* Search */,
@ -1501,6 +1514,7 @@
packageProductDependencies = ( packageProductDependencies = (
4C649880286E0EE300EAE2B3 /* secp256k1 */, 4C649880286E0EE300EAE2B3 /* secp256k1 */,
4C06670328FC7EC500038D2A /* Kingfisher */, 4C06670328FC7EC500038D2A /* Kingfisher */,
4CCF9AB12A1FE80C00E03CFB /* GSPlayer */,
); );
productName = damus; productName = damus;
productReference = 4CE6DEE327F7A08100C66700 /* damus.app */; productReference = 4CE6DEE327F7A08100C66700 /* damus.app */;
@ -1605,6 +1619,7 @@
packageReferences = ( packageReferences = (
4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */, 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */,
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */, 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */,
4CCF9AB02A1FE80B00E03CFB /* XCRemoteSwiftPackageReference "GSPlayer" */,
); );
productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */; productRefGroup = 4CE6DEE427F7A08100C66700 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -1669,6 +1684,7 @@
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */, 4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
4C216F34286F5ACD00040376 /* DMView.swift in Sources */, 4C216F34286F5ACD00040376 /* DMView.swift in Sources */,
4C3EA64428FF558100C48A62 /* sha256.c in Sources */, 4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
4CCF9AAF2A1FDBDB00E03CFB /* VideoPlayer.swift in Sources */,
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */, 4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */,
4C363AA828297703006E126D /* InsertSort.swift in Sources */, 4C363AA828297703006E126D /* InsertSort.swift in Sources */,
4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */, 4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */,
@ -2441,6 +2457,14 @@
revision = 40b4b38b3b1c83f7088c76189a742870e0ca06a9; revision = 40b4b38b3b1c83f7088c76189a742870e0ca06a9;
}; };
}; };
4CCF9AB02A1FE80B00E03CFB /* XCRemoteSwiftPackageReference "GSPlayer" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/wxxsw/GSPlayer";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.2.26;
};
};
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@ -2454,6 +2478,11 @@
package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */; package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */;
productName = secp256k1; productName = secp256k1;
}; };
4CCF9AB12A1FE80C00E03CFB /* GSPlayer */ = {
isa = XCSwiftPackageProductDependency;
package = 4CCF9AB02A1FE80B00E03CFB /* XCRemoteSwiftPackageReference "GSPlayer" */;
productName = GSPlayer;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */ /* Begin XCVersionGroup section */

View File

@ -1,5 +1,14 @@
{ {
"pins" : [ "pins" : [
{
"identity" : "gsplayer",
"kind" : "remoteSourceControl",
"location" : "https://github.com/wxxsw/GSPlayer.git",
"state" : {
"revision" : "aa6dad7943d52f5207f7fcc2ad3e4274583443b8",
"version" : "0.2.26"
}
},
{ {
"identity" : "kingfisher", "identity" : "kingfisher",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@ -0,0 +1,326 @@
//
// VideoPlayer.swift
// damus
//
// Created by William Casarin on 2023-05-25.
//
import Foundation
//
// VideoPlayer.swift
// VideoPlayer
//
// Created by Gesen on 2019/7/7.
// Copyright © 2019 Gesen. All rights reserved.
//
import AVFoundation
import GSPlayer
import SwiftUI
public enum VideoState {
/// From the first load to get the first frame of the video
case loading
/// Playing now
case playing(totalDuration: Double)
/// Pause, will be called repeatedly when the buffer progress changes
case paused(playProgress: Double, bufferProgress: Double)
/// An error occurred and cannot continue playing
case error(NSError)
}
enum VideoHandler {
case onBufferChanged((Double) -> Void)
case onPlayToEndTime(() -> Void)
case onReplay(() -> Void)
case onStateChanged((VideoState) -> Void)
}
public class VideoPlayerModel: ObservableObject {
@Published var autoReplay: Bool = true
@Published var muted: Bool = true
@Published var play: Bool = true
@Published var size: CGSize? = nil
@Published var contentMode: UIView.ContentMode = .scaleAspectFill
var time: CMTime = CMTime()
var handlers: [VideoHandler] = []
init() {
}
func stop() {
self.play = false
}
func start() {
self.play = true
}
func mute() {
self.muted = true
}
func unmute() {
self.muted = false
}
/// Whether the video will be automatically replayed until the end of the video playback.
func autoReplay(_ value: Bool) -> Self {
autoReplay = value
return self
}
/// Whether the video is muted, only for this instance.
func mute(_ value: Bool) -> Self {
muted = value
return self
}
/// A string defining how the video is displayed within an AVPlayerLayer bounds rect.
/// scaleAspectFill -> resizeAspectFill, scaleAspectFit -> resizeAspect, other -> resize
func contentMode(_ value: UIView.ContentMode) -> Self {
contentMode = value
return self
}
/// Trigger a callback when the buffer progress changes,
/// the value is between 0 and 1.
func onBufferChanged(_ handler: @escaping (Double) -> Void) -> Self {
self.handlers.append(.onBufferChanged(handler))
return self
}
/// Playing to the end.
func onPlayToEndTime(_ handler: @escaping () -> Void) -> Self {
self.handlers.append(.onPlayToEndTime(handler))
return self
}
/// Replay after playing to the end.
func onReplay(_ handler: @escaping () -> Void) -> Self {
self.handlers.append(.onReplay(handler))
return self
}
/// Playback status changes, such as from play to pause.
func onStateChanged(_ handler: @escaping (VideoState) -> Void) -> Self {
self.handlers.append(.onStateChanged(handler))
return self
}
}
@available(iOS 13, *)
public struct VideoPlayer {
private(set) var url: URL
@ObservedObject var model: VideoPlayerModel
/// Init video player instance.
/// - Parameters:
/// - url: http/https URL
/// - play: play/pause
/// - time: current time
public init(url: URL, model: VideoPlayerModel) {
self.url = url
self._model = ObservedObject(wrappedValue: model)
}
}
@available(iOS 13, *)
public extension VideoPlayer {
/// Set the preload size, the default value is 1024 * 1024, unit is byte.
static var preloadByteCount: Int {
get { VideoPreloadManager.shared.preloadByteCount }
set { VideoPreloadManager.shared.preloadByteCount = newValue }
}
/// Set the video urls to be preload queue.
/// Preloading will automatically cache a short segment of the beginning of the video
/// and decide whether to start or pause the preload based on the buffering of the currently playing video.
/// - Parameter urls: URL array
static func preload(urls: [URL]) {
VideoPreloadManager.shared.set(waiting: urls)
}
/// Set custom http header, such as token.
static func customHTTPHeaderFields(transform: @escaping (URL) -> [String: String]?) {
VideoLoadManager.shared.customHTTPHeaderFields = transform
}
/// Get the total size of the video cache.
static func calculateCachedSize() -> UInt {
return VideoCacheManager.calculateCachedSize()
}
/// Clean up all caches.
static func cleanAllCache() {
try? VideoCacheManager.cleanAllCache()
}
}
@available(iOS 13, *)
public extension VideoPlayer {
}
@available(iOS 13, *)
extension VideoPlayer: UIViewRepresentable {
public func makeUIView(context: Context) -> VideoPlayerView {
let uiView = VideoPlayerView()
uiView.playToEndTime = {
if self.model.autoReplay == false {
self.model.play = false
}
DispatchQueue.main.async {
for handler in model.handlers {
if case .onPlayToEndTime(let cb) = handler {
cb()
}
}
}
}
uiView.contentMode = self.model.contentMode
uiView.replay = {
DispatchQueue.main.async {
for handler in model.handlers {
if case .onReplay(let cb) = handler {
cb()
}
}
}
}
uiView.stateDidChanged = { [unowned uiView] _ in
let state: VideoState = uiView.convertState()
if case .playing = state {
context.coordinator.startObserver(uiView: uiView)
} else {
context.coordinator.stopObserver(uiView: uiView)
}
if model.size == nil, let size = uiView.player?.currentImage?.size {
model.size = size
}
DispatchQueue.main.async {
for handler in model.handlers {
if case .onStateChanged(let cb) = handler {
cb(state)
}
}
}
}
return uiView
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
public func updateUIView(_ uiView: VideoPlayerView, context: Context) {
if context.coordinator.observingURL != url {
context.coordinator.clean()
context.coordinator.observingURL = url
}
if model.play {
uiView.play(for: url)
} else {
uiView.pause(reason: .userInteraction)
}
print("intrinsic video size \(uiView.intrinsicContentSize)")
uiView.isMuted = model.muted
uiView.isAutoReplay = model.autoReplay
if let observerTime = context.coordinator.observerTime, model.time != observerTime {
uiView.seek(to: model.time, toleranceBefore: model.time, toleranceAfter: model.time, completion: { _ in })
}
}
public static func dismantleUIView(_ uiView: VideoPlayerView, coordinator: VideoPlayer.Coordinator) {
uiView.pause(reason: .hidden)
}
public class Coordinator: NSObject {
var videoPlayer: VideoPlayer
var observingURL: URL?
var observer: Any?
var observerTime: CMTime?
var observerBuffer: Double?
init(_ videoPlayer: VideoPlayer) {
self.videoPlayer = videoPlayer
}
func startObserver(uiView: VideoPlayerView) {
guard observer == nil else { return }
observer = uiView.addPeriodicTimeObserver(forInterval: .init(seconds: 0.25, preferredTimescale: 60)) { [weak self, unowned uiView] time in
guard let `self` = self else { return }
self.videoPlayer.model.time = time
self.observerTime = time
self.updateBuffer(uiView: uiView)
}
}
func stopObserver(uiView: VideoPlayerView) {
guard let observer = observer else { return }
uiView.removeTimeObserver(observer)
self.observer = nil
}
func clean() {
self.observingURL = nil
self.observer = nil
self.observerTime = nil
self.observerBuffer = nil
}
func updateBuffer(uiView: VideoPlayerView) {
let bufferProgress = uiView.bufferProgress
guard bufferProgress != observerBuffer else { return }
for handler in videoPlayer.model.handlers {
if case .onBufferChanged(let cb) = handler {
DispatchQueue.main.async {
cb(bufferProgress)
}
}
}
observerBuffer = bufferProgress
}
}
}
private extension VideoPlayerView {
func convertState() -> VideoState {
switch state {
case .none, .loading:
return .loading
case .playing:
return .playing(totalDuration: totalDuration)
case .paused(let p, let b):
return .paused(playProgress: p, bufferProgress: b)
case .error(let error):
return .error(error)
}
}
}