diff --git a/kplayer.xcodeproj/project.pbxproj b/kplayer.xcodeproj/project.pbxproj index 51eea10..f64d130 100644 --- a/kplayer.xcodeproj/project.pbxproj +++ b/kplayer.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1C7360C0F2A4F0214FE353BD /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7367ECBD369A2A0C94C499 /* FileHelper.swift */; }; + 1C7360F1D2CF83ECDD586B84 /* BMPlayerCompositionResourceDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73610B997EBA367C806C1B /* BMPlayerCompositionResourceDefinition.swift */; }; 1C7361D2B6E0AE689FAAF4F4 /* VideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736C7FFBDAC665AE04CB65 /* VideoController.swift */; }; 1C7362A6FA1C5DA0B0677F1E /* readme.md in Sources */ = {isa = PBXBuildFile; fileRef = 1C736871C9B012CB704AB803 /* readme.md */; }; 1C73631EACF56BABD3B2BCFB /* LayoutTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736BC4450890C45F8FBC63 /* LayoutTools.swift */; }; @@ -73,6 +74,7 @@ 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = ""; }; 1C7360AE55EB115762C42EB9 /* BMTimeSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMTimeSlider.swift; sourceTree = ""; }; 1C7360D6580FB5D09C2BBCCB /* BMPlayerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerManager.swift; sourceTree = ""; }; + 1C73610B997EBA367C806C1B /* BMPlayerCompositionResourceDefinition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerCompositionResourceDefinition.swift; sourceTree = ""; }; 1C73611D226B48C24DB37535 /* MasterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; 1C73620D01687FB4F1811C5C /* NetworkHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkHelper.swift; sourceTree = ""; }; 1C736253AB7A95EA41B605B7 /* ItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemModel.swift; sourceTree = ""; }; @@ -213,6 +215,7 @@ 1C7366AAB82A46086690E164 /* BMSubtitles.swift */, 1C7360AE55EB115762C42EB9 /* BMTimeSlider.swift */, 1C7360D6580FB5D09C2BBCCB /* BMPlayerManager.swift */, + 1C73610B997EBA367C806C1B /* BMPlayerCompositionResourceDefinition.swift */, ); path = video; sourceTree = ""; @@ -502,6 +505,7 @@ 1C7360C0F2A4F0214FE353BD /* FileHelper.swift in Sources */, 1C7366A0CFD2B55BF8C3BAF0 /* NetworkDelegate.swift in Sources */, 1C7368242038C0FF6C9631E7 /* VideoHelper.swift in Sources */, + 1C7360F1D2CF83ECDD586B84 /* BMPlayerCompositionResourceDefinition.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/kplayer/core/MediaItem.swift b/kplayer/core/MediaItem.swift index cd8cc16..f46cf12 100644 --- a/kplayer/core/MediaItem.swift +++ b/kplayer/core/MediaItem.swift @@ -136,6 +136,10 @@ class MediaItem: CustomDebugStringConvertible { Absolute URL, unter der das Thumbnail des Items abgerufen werden kann. */ var thumbUrlAbsolute: String { + if thumbUrl!.starts(with: "file:") { + return thumbUrl! + } + let enc = thumbUrl!.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)! return NetworkManager.sharedInstance.baseurl + "/service/download" + enc } @@ -161,9 +165,20 @@ class MediaItem: CustomDebugStringConvertible { file = file.appendingPathComponent(name) return file } + let enc = name.replacingOccurrences(of: " ", with: "%20") - let index = encodedDir!.index(encodedDir!.startIndex, offsetBy: 10) - let s = NetworkManager.sharedInstance.vidurl + encodedDir!.substring(from:index) + + var encoded : String + + if encodedDir!.count >= 10 { + let index = encodedDir!.index(encodedDir!.startIndex, offsetBy: 10) + encoded = encodedDir!.substring(from:index) + } + else { + encoded = encodedDir! + } + + let s = NetworkManager.sharedInstance.vidurl + encoded if s.endsWith("/") { return URL(string: s + enc) diff --git a/kplayer/core/NetworkManager.swift b/kplayer/core/NetworkManager.swift index 9f9a1fc..ec50be8 100644 --- a/kplayer/core/NetworkManager.swift +++ b/kplayer/core/NetworkManager.swift @@ -58,6 +58,7 @@ class NetworkManager { let m = MediaItem(name: fileURL.lastPathComponent, path: path, root: "fav", type: ItemType.VIDEO) m.local = true + m.thumbUrl = fileURL.appendingPathExtension("jpg").absoluteString res.append(m) diff --git a/kplayer/detail/DetailViewController.swift b/kplayer/detail/DetailViewController.swift index 277e310..da90b02 100644 --- a/kplayer/detail/DetailViewController.swift +++ b/kplayer/detail/DetailViewController.swift @@ -9,6 +9,7 @@ import UIKit import Alamofire import FileBrowser +import AVFoundation protocol DetailDelegate { func loadDetails(selectedItem: MediaItem, completionHandler: @escaping () -> Void) @@ -112,11 +113,16 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout } @objc func overview() { - let pc = MediaPhotoController() var i = [MediaItem]() if let d = detailItem { + if (d.local) { + showComposition(d) + return + } + let pc = MediaPhotoController() + for it in d.children { if it.children.count > 1 || !showFavoritesOnly { for c in it.children { @@ -136,6 +142,35 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout } } + private func showComposition(_ item: MediaItem) { + var assets = [URL]() + + for d in item.children { + assets.append(d.playerURL!) + } + + let vc = VideoController() + + let item = MediaItem(name: item.name, path: item.path, root: item.root, type: ItemType.VIDEO) + + vc.setItems(items: [item]) + vc.setCurrentItem(item: item) + vc.urls = assets + + vc.setCompletionHandler(handler: { + self.dismiss(animated: true, completion: nil); + }) + + let navController = UINavigationController(rootViewController: (vc as UIViewController)) + navController.modalPresentationStyle = .fullScreen + navController.modalPresentationCapturesStatusBarAppearance = true + navController.navigationBar.barTintColor = UIColor.black + (vc as UIViewController).navigationItem.leftItemsSupplementBackButton = true + + self.present(navController, animated: false, completion: nil) + + } + @objc func refreshItems(_ notification: Notification) { if notification.object == nil { diff --git a/kplayer/detail/VideoController.swift b/kplayer/detail/VideoController.swift index 4b3e6e3..7616ed6 100644 --- a/kplayer/detail/VideoController.swift +++ b/kplayer/detail/VideoController.swift @@ -49,6 +49,8 @@ class VideoController: UIViewController, ItemController, BMPlayerDelegate { var allowEdit = true var index = 0 + var urls : [URL]? + override func viewDidLoad() { super.viewDidLoad() @@ -83,7 +85,7 @@ class VideoController: UIViewController, ItemController, BMPlayerDelegate { if let c = currentItem, let url = c.playerURL { print(url) play(url as URL) - player.playerLayer!.player!.volume = 0.0 + // player.playerLayer!.player!.volume = 0.0 update() } @@ -179,16 +181,23 @@ class VideoController: UIViewController, ItemController, BMPlayerDelegate { VideoHelper.export(item: player.avPlayer!.currentItem!, clipStart: loopStart, clipDuration: dur, file: file) { url in self.showAlert(title: c.name, message: "saved") } - var s = c; + var s : MediaModel; if (c.type == ItemType.SNAPSHOT && c.parent != nil) { - s = c.parent! + s = c.parent!.toMediaModel() + let local = try Data(contentsOf: URL(string: c.thumbUrlAbsolute)!) + let tfile = FileHelper.getDocumentsDirectory().appendingPathComponent(name).appendingPathComponent(s.name).appendingPathExtension("jpg") + try local.write(to: tfile) + } + else { + s = c.toMediaModel() } - let jfile = FileHelper.getDocumentsDirectory().appendingPathComponent(name).appendingPathComponent(c.name).appendingPathExtension("json") - - let json = s.toJSON() + let jfile = FileHelper.getDocumentsDirectory().appendingPathComponent(name).appendingPathComponent(s.name).appendingPathExtension("json") + let jsonEncoder = JSONEncoder() + let jsonData = try! jsonEncoder.encode(s) + let json = String(data: jsonData, encoding: String.Encoding.utf8) print(json) - try json.write(to: jfile, atomically: true, encoding: .utf8) + try json!.write(to: jfile, atomically: true, encoding: .utf8) } catch { print(error) } @@ -199,20 +208,14 @@ class VideoController: UIViewController, ItemController, BMPlayerDelegate { player.pause() } else { + if player.isPlayToTheEnd { + player.seek(0, completion: {[weak self] in + self!.player.play() + }) + player.isPlayToTheEnd = false + } player.play() } -// if moviePlayer!.playbackState == MPMoviePlaybackState.playing { -// moviePlayer!.pause() -// } -// else { -// moviePlayer!.play() -// -// Timer.scheduledTimer(timeInterval: 0.5, -// target: self, -// selector: #selector(resumePlay), -// userInfo: nil, -// repeats: false) -// } print("play") } @@ -300,12 +303,20 @@ print("play") } for i in allItems { - let r = BMPlayerResourceDefinition(url: i.playerURL!, definition: i.name); - def.append(r) - if (url == i.playerURL) { - index = count + if let a = urls { + let r = BMPlayerCompositionResourceDefinition(url: i.playerURL!, definition: i.name) + r.assets = a + def.append(r) + } + else { + + let r = BMPlayerResourceDefinition(url: i.playerURL!, definition: i.name); + def.append(r) + if (url == i.playerURL) { + index = count + } + count += 1 } - count += 1 } let asset = BMPlayerResource(name: "video", definitions: def) // let asset = BMPlayerResource(url: url) diff --git a/kplayer/master/MasterViewController.swift b/kplayer/master/MasterViewController.swift index ea25164..831165f 100644 --- a/kplayer/master/MasterViewController.swift +++ b/kplayer/master/MasterViewController.swift @@ -302,6 +302,10 @@ class MasterViewController: UITableViewController, UISearchResultsUpdating, UITa do { try FileManager.default.moveItem(at: at, to: to) try FileManager.default.moveItem(at: at.appendingPathExtension("json"), to: to.appendingPathExtension("json")) + do { + try FileManager.default.moveItem(at: at.appendingPathExtension("jpg"), to: to.appendingPathExtension("jpg")) + } catch { + } let weiter:Weiter = { (g) in diff --git a/kplayer/photo/PhotoController.swift b/kplayer/photo/PhotoController.swift index b3d02cf..84aa700 100644 --- a/kplayer/photo/PhotoController.swift +++ b/kplayer/photo/PhotoController.swift @@ -47,8 +47,10 @@ class MediaPhotoController: NIToolbarPhotoViewController, NIPhotoAlbumScrollView override func setChromeTitle() { let idx = photoAlbumView.centerPageIndex - let selected = items[idx] - title = "(\(idx + 1) / \(photoAlbumView.numberOfPages)) \(selected.path) \(selected.name)" + if (items.count > idx) { + let selected = items[idx] + title = "(\(idx + 1) / \(photoAlbumView.numberOfPages)) \(selected.path) \(selected.name)" + } } override func viewDidLoad() { diff --git a/kplayer/util/VideoHelper.swift b/kplayer/util/VideoHelper.swift index 91b6ddf..1f4efdb 100644 --- a/kplayer/util/VideoHelper.swift +++ b/kplayer/util/VideoHelper.swift @@ -19,8 +19,8 @@ class VideoHelper { let sourceVideoTrack = item.asset.tracks(withMediaType: AVMediaType.video).first! let sourceAudioTrack = item.asset.tracks(withMediaType: AVMediaType.audio).first! - let start = CMTime(seconds: clipStart, preferredTimescale: 1); - let dur = CMTime(seconds: clipDuration, preferredTimescale: 100); + let start = CMTime(seconds: clipStart, preferredTimescale: 10000); + let dur = CMTime(seconds: clipDuration, preferredTimescale: 10000); do { try compositionVideoTrack!.insertTimeRange(CMTimeRangeMake(start: start, duration: dur), of: sourceVideoTrack, at: start) @@ -61,4 +61,26 @@ class VideoHelper { completion(file) } } + + static func combine(urls: [URL]) -> AVAsset { + let c = AVMutableComposition() + let tr = c.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) + + var currentTime = CMTime.zero + + do { + for url in urls { + var urlAsset = AVURLAsset(url: url) + + let range = CMTimeRangeMake(start: CMTime.zero, duration: urlAsset.duration) + try tr!.insertTimeRange(range, of: urlAsset.tracks(withMediaType: AVMediaType.video)[0], at: currentTime) + + currentTime = CMTimeAdd(currentTime, urlAsset.duration) + } + } catch { + print(error) + } + + return c + } } diff --git a/kplayer/video/BMPlayer.swift b/kplayer/video/BMPlayer.swift index c7aba0c..e5c2ccf 100644 --- a/kplayer/video/BMPlayer.swift +++ b/kplayer/video/BMPlayer.swift @@ -157,7 +157,7 @@ open class BMPlayer: UIView { fileprivate var isMaskShowing = false fileprivate var isSlowed = false fileprivate var isMirrored = false - fileprivate var isPlayToTheEnd = false + var isPlayToTheEnd = false //视频画面比例 fileprivate var aspectRatio: BMPlayerAspectRatio = .default diff --git a/kplayer/video/BMPlayerCompositionResourceDefinition.swift b/kplayer/video/BMPlayerCompositionResourceDefinition.swift new file mode 100644 index 0000000..5120de7 --- /dev/null +++ b/kplayer/video/BMPlayerCompositionResourceDefinition.swift @@ -0,0 +1,19 @@ +// +// Created by Marco Schmickler on 05.05.21. +// Copyright (c) 2021 Marco Schmickler. All rights reserved. +// + +import Foundation +import AVFoundation + +class BMPlayerCompositionResourceDefinition : BMPlayerResourceDefinition { + var assets: [URL]? + + override var avURLAsset: AVAsset { + get { + let asset = VideoHelper.combine(urls: assets!) + + return asset + } + } +} diff --git a/kplayer/video/BMPlayerItem.swift b/kplayer/video/BMPlayerItem.swift index 5d6178b..7379cad 100644 --- a/kplayer/video/BMPlayerItem.swift +++ b/kplayer/video/BMPlayerItem.swift @@ -59,7 +59,7 @@ open class BMPlayerResourceDefinition { /// An instance of NSDictionary that contains keys for specifying options for the initialization of the AVURLAsset. See AVURLAssetPreferPreciseDurationAndTimingKey and AVURLAssetReferenceRestrictionsKey above. public var options: [String : Any]? - open var avURLAsset: AVURLAsset { + open var avURLAsset: AVAsset { get { guard !url.isFileURL, url.pathExtension != "m3u8" else { return AVURLAsset(url: url) diff --git a/kplayer/video/BMPlayerLayerView.swift b/kplayer/video/BMPlayerLayerView.swift index 7d7a502..2376de0 100644 --- a/kplayer/video/BMPlayerLayerView.swift +++ b/kplayer/video/BMPlayerLayerView.swift @@ -96,7 +96,7 @@ open class BMPlayerLayerView: UIView { /// 计时器 var timer: Timer? - fileprivate var urlAsset: AVURLAsset? + fileprivate var urlAsset: AVAsset? fileprivate var lastPlayerItem: AVPlayerItem? /// playerLayer @@ -137,7 +137,7 @@ open class BMPlayerLayerView: UIView { playAsset(asset: asset) } - open func playAsset(asset: AVURLAsset) { + open func playAsset(asset: AVAsset) { urlAsset = asset onSetVideoAsset() play()