Explorar o código

Merge pull request #2397 from nextcloud/vlc

Vlc
Marino Faggiana hai 1 ano
pai
achega
56d780590a

+ 1 - 1
Brand/Database.swift

@@ -26,4 +26,4 @@ import Foundation
 // Database Realm
 //
 let databaseName                    = "nextcloud.realm"
-let databaseSchemaVersion: UInt64   = 278
+let databaseSchemaVersion: UInt64   = 281

+ 1 - 1
Cartfile

@@ -1,2 +1,2 @@
-github "https://github.com/marinofaggiana/KTVHTTPCache" "2.0.6"
 github "https://github.com/marinofaggiana/TOPasscodeViewController" "master"
+binary "https://code.videolan.org/videolan/VLCKit/raw/master/Packaging/MobileVLCKit.json" ~> 3.3.0

+ 1 - 0
Cartfile.resolved

@@ -1,2 +1,3 @@
+binary "https://code.videolan.org/videolan/VLCKit/raw/master/Packaging/MobileVLCKit.json" "3.5.1"
 github "marinofaggiana/KTVHTTPCache" "2.0.6"
 github "marinofaggiana/TOPasscodeViewController" "ed795637acd2b1ef154e011a04ebab4686d0523c"

+ 8 - 30
Nextcloud.xcodeproj/project.pbxproj

@@ -140,7 +140,6 @@
 		F7148059262ED52200693E51 /* NCSectionHeaderMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F78ACD57219048040088454D /* NCSectionHeaderMenu.xib */; };
 		F714805E262ED52900693E51 /* NCSectionFooter.xib in Resources */ = {isa = PBXBuildFile; fileRef = F78ACD53219047D40088454D /* NCSectionFooter.xib */; };
 		F7148063262ED66200693E51 /* NCEmptyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7239876253D86D300257F49 /* NCEmptyView.xib */; };
-		F716B75F26F09DF600D37EFC /* NCKTVHTTPCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F716B75E26F09DF600D37EFC /* NCKTVHTTPCache.swift */; };
 		F717402D24F699A5000C87D5 /* NCFavorite.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F717402B24F699A5000C87D5 /* NCFavorite.storyboard */; };
 		F717402E24F699A5000C87D5 /* NCFavorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = F717402C24F699A5000C87D5 /* NCFavorite.swift */; };
 		F718C24E254D507B00C5C256 /* NCViewerMediaDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F718C24D254D507B00C5C256 /* NCViewerMediaDetailView.swift */; };
@@ -328,10 +327,6 @@
 		F76D3CF12428B40E005DFA87 /* NCViewerPDFSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76D3CF02428B40E005DFA87 /* NCViewerPDFSearch.swift */; };
 		F76D3CF32428B94E005DFA87 /* NCViewerPDFSearchCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F76D3CF22428B94E005DFA87 /* NCViewerPDFSearchCell.xib */; };
 		F76D3CF52428D0C1005DFA87 /* NCViewerPDF.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F76D3CF42428D0C0005DFA87 /* NCViewerPDF.storyboard */; };
-		F76DA93F277B75870082465B /* KTVCocoaHTTPServer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = F70B867E2642CF5400ED5349 /* KTVCocoaHTTPServer.xcframework */; };
-		F76DA940277B75870082465B /* KTVCocoaHTTPServer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F70B867E2642CF5400ED5349 /* KTVCocoaHTTPServer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
-		F76DA941277B75870082465B /* KTVHTTPCache.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = F70B86792642CF5300ED5349 /* KTVHTTPCache.xcframework */; };
-		F76DA942277B75870082465B /* KTVHTTPCache.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F70B86792642CF5300ED5349 /* KTVHTTPCache.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		F76DA95B277B75A90082465B /* TOPasscodeViewController.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = F70B86822642CF5500ED5349 /* TOPasscodeViewController.xcframework */; };
 		F76DA95C277B75A90082465B /* TOPasscodeViewController.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F70B86822642CF5500ED5349 /* TOPasscodeViewController.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		F76DA963277B760E0082465B /* Queuer in Frameworks */ = {isa = PBXBuildFile; productRef = F76DA962277B760E0082465B /* Queuer */; };
@@ -360,6 +355,8 @@
 		F77444F8222816D5000D5EB0 /* NCPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77444F7222816D5000D5EB0 /* NCPickerViewController.swift */; };
 		F77910A525DD517B00CEDB9E /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = F77910A425DD517B00CEDB9E /* Settings.bundle */; };
 		F77910AB25DD53C700CEDB9E /* NCSettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77910AA25DD53C700CEDB9E /* NCSettingsBundleHelper.swift */; };
+		F7792DE529EEE02D005930CE /* MobileVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7792DE429EEE02D005930CE /* MobileVLCKit.xcframework */; };
+		F7792DE629EEE02D005930CE /* MobileVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F7792DE429EEE02D005930CE /* MobileVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		F77A697D250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77A697C250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift */; };
 		F77B0DF51D118A16002130FE /* CCUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = F7053E3D1C639DF500741EA5 /* CCUtility.m */; };
 		F77B0E4F1D118A16002130FE /* CCManageAutoUpload.m in Sources */ = {isa = PBXBuildFile; fileRef = F7ACE42F1BAC0268006C0017 /* CCManageAutoUpload.m */; };
@@ -587,8 +584,6 @@
 		F7EFC0C6256BC77700461AAD /* NCMoreUserCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7EFC0C5256BC77700461AAD /* NCMoreUserCell.xib */; };
 		F7EFC0CD256BF8DD00461AAD /* NCUserStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7EFC0CC256BF8DD00461AAD /* NCUserStatus.swift */; };
 		F7F1E54C2492369A00E42386 /* NCMediaCommandView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */; };
-		F7F4F0F727ECDBA4008676F9 /* NCSubtitles.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F4F0F327ECDBA4008676F9 /* NCSubtitles.swift */; };
-		F7F4F0F927ECDBA4008676F9 /* NCSubtitlePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7F4F0F527ECDBA4008676F9 /* NCSubtitlePlayer.swift */; };
 		F7F4F10527ECDBDB008676F9 /* Inconsolata-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F7F4F0FD27ECDBDB008676F9 /* Inconsolata-SemiBold.ttf */; };
 		F7F4F10627ECDBDB008676F9 /* Inconsolata-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F7F4F0FE27ECDBDB008676F9 /* Inconsolata-Medium.ttf */; };
 		F7F4F10727ECDBDB008676F9 /* Inconsolata-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F7F4F0FF27ECDBDB008676F9 /* Inconsolata-Black.ttf */; };
@@ -665,9 +660,8 @@
 			dstPath = "";
 			dstSubfolderSpec = 10;
 			files = (
-				F76DA942277B75870082465B /* KTVHTTPCache.xcframework in Embed Frameworks */,
+				F7792DE629EEE02D005930CE /* MobileVLCKit.xcframework in Embed Frameworks */,
 				F76DA95C277B75A90082465B /* TOPasscodeViewController.xcframework in Embed Frameworks */,
-				F76DA940277B75870082465B /* KTVCocoaHTTPServer.xcframework in Embed Frameworks */,
 			);
 			name = "Embed Frameworks";
 			runOnlyForDeploymentPostprocessing = 0;
@@ -834,7 +828,6 @@
 		F7151A811D477A4B00E6AF45 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
 		F7169A301EE59BB70086BD69 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
 		F7169A4C1EE59C640086BD69 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
-		F716B75E26F09DF600D37EFC /* NCKTVHTTPCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCKTVHTTPCache.swift; sourceTree = "<group>"; };
 		F716FE7723795E5000FABE50 /* NCCommunication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NCCommunication.framework; path = Carthage/Build/iOS/NCCommunication.framework; sourceTree = "<group>"; };
 		F717402B24F699A5000C87D5 /* NCFavorite.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCFavorite.storyboard; sourceTree = "<group>"; };
 		F717402C24F699A5000C87D5 /* NCFavorite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCFavorite.swift; sourceTree = "<group>"; };
@@ -1007,6 +1000,7 @@
 		F77444F7222816D5000D5EB0 /* NCPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCPickerViewController.swift; sourceTree = "<group>"; };
 		F77910A425DD517B00CEDB9E /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
 		F77910AA25DD53C700CEDB9E /* NCSettingsBundleHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCSettingsBundleHelper.swift; sourceTree = "<group>"; };
+		F7792DE429EEE02D005930CE /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; };
 		F77A697C250A0FBC00FF1708 /* NCCollectionViewCommon+Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+Menu.swift"; sourceTree = "<group>"; };
 		F77BB745289984CA0090FC19 /* UIViewController+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = "<group>"; };
 		F77BB747289985270090FC19 /* UITabBarController+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITabBarController+Extension.swift"; sourceTree = "<group>"; };
@@ -1236,8 +1230,6 @@
 		F7EFC0CC256BF8DD00461AAD /* NCUserStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCUserStatus.swift; sourceTree = "<group>"; };
 		F7F1E54B2492369A00E42386 /* NCMediaCommandView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NCMediaCommandView.xib; sourceTree = "<group>"; };
 		F7F35B592578FB63003F5589 /* CollaboraOnlineWebViewKeyboardManager.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CollaboraOnlineWebViewKeyboardManager.framework; path = Carthage/Build/iOS/CollaboraOnlineWebViewKeyboardManager.framework; sourceTree = "<group>"; };
-		F7F4F0F327ECDBA4008676F9 /* NCSubtitles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCSubtitles.swift; sourceTree = "<group>"; };
-		F7F4F0F527ECDBA4008676F9 /* NCSubtitlePlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCSubtitlePlayer.swift; sourceTree = "<group>"; };
 		F7F4F0FD27ECDBDB008676F9 /* Inconsolata-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inconsolata-SemiBold.ttf"; sourceTree = "<group>"; };
 		F7F4F0FE27ECDBDB008676F9 /* Inconsolata-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inconsolata-Medium.ttf"; sourceTree = "<group>"; };
 		F7F4F0FF27ECDBDB008676F9 /* Inconsolata-Black.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Inconsolata-Black.ttf"; sourceTree = "<group>"; };
@@ -1329,7 +1321,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				F7D56B1A2972405500FA46C4 /* Mantis in Frameworks */,
-				F76DA941277B75870082465B /* KTVHTTPCache.xcframework in Frameworks */,
 				F7ED547C25EEA65400956C55 /* QRCodeReader in Frameworks */,
 				F788ECC7263AAAFA00ADC67F /* MarkdownKit in Frameworks */,
 				F77BC3EB293E5268005F2B08 /* Swifter in Frameworks */,
@@ -1341,6 +1332,7 @@
 				F758A01227A7F03E0069468B /* JGProgressHUD in Frameworks */,
 				F77333882927A72100466E35 /* OpenSSL in Frameworks */,
 				F76DA966277B76F30082465B /* UICKeyChainStore in Frameworks */,
+				F7792DE529EEE02D005930CE /* MobileVLCKit.xcframework in Frameworks */,
 				F753BA93281FD8020015BFB6 /* EasyTipView in Frameworks */,
 				F76DA95B277B75A90082465B /* TOPasscodeViewController.xcframework in Frameworks */,
 				F76DA963277B760E0082465B /* Queuer in Frameworks */,
@@ -1353,7 +1345,6 @@
 				F72DA9B425F53E4E00B87DB1 /* SwiftRichString in Frameworks */,
 				F74E7720277A2EF40013B958 /* XLForm in Frameworks */,
 				F73ADD1C265546890069EA0D /* SwiftEntryKit in Frameworks */,
-				F76DA93F277B75870082465B /* KTVCocoaHTTPServer.xcframework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1827,11 +1818,11 @@
 		F79018B1240962C7007C9B6D /* NCViewerMedia */ = {
 			isa = PBXGroup;
 			children = (
-				F79EDA9E26B004980007D134 /* NCPlayer */,
+				F718C24D254D507B00C5C256 /* NCViewerMediaDetailView.swift */,
 				F70753F62542A9C000972D44 /* NCViewerMediaPage.storyboard */,
 				F70753EA2542A99800972D44 /* NCViewerMediaPage.swift */,
-				F718C24D254D507B00C5C256 /* NCViewerMediaDetailView.swift */,
 				F70753F02542A9A200972D44 /* NCViewerMedia.swift */,
+				F79EDA9E26B004980007D134 /* NCPlayer */,
 			);
 			path = NCViewerMedia;
 			sourceTree = "<group>";
@@ -1873,11 +1864,9 @@
 		F79EDA9E26B004980007D134 /* NCPlayer */ = {
 			isa = PBXGroup;
 			children = (
-				F7F4F0F227ECDBA4008676F9 /* NCSubtitle */,
 				F79EDAA126B004980007D134 /* NCPlayer.swift */,
 				F732D23227CF8AED000B0F1B /* NCPlayerToolBar.xib */,
 				F79EDA9F26B004980007D134 /* NCPlayerToolBar.swift */,
-				F716B75E26F09DF600D37EFC /* NCKTVHTTPCache.swift */,
 			);
 			path = NCPlayer;
 			sourceTree = "<group>";
@@ -2165,15 +2154,6 @@
 			path = UserStatus;
 			sourceTree = "<group>";
 		};
-		F7F4F0F227ECDBA4008676F9 /* NCSubtitle */ = {
-			isa = PBXGroup;
-			children = (
-				F7F4F0F327ECDBA4008676F9 /* NCSubtitles.swift */,
-				F7F4F0F527ECDBA4008676F9 /* NCSubtitlePlayer.swift */,
-			);
-			path = NCSubtitle;
-			sourceTree = "<group>";
-		};
 		F7F4F0FB27ECDBDA008676F9 /* Font */ = {
 			isa = PBXGroup;
 			children = (
@@ -2284,6 +2264,7 @@
 		F7FC7D541DC1F93700BB2C6A /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				F7792DE429EEE02D005930CE /* MobileVLCKit.xcframework */,
 				F783031028B4C86200B84583 /* libc++.1.tbd */,
 				F783031128B4C86200B84583 /* libc++abi.tbd */,
 				F783030E28B4C83F00B84583 /* libc++.tbd */,
@@ -3209,7 +3190,6 @@
 				370D26AF248A3D7A00121797 /* NCCellProtocol.swift in Sources */,
 				F77B0DF51D118A16002130FE /* CCUtility.m in Sources */,
 				F70D87D025EE6E58008CBBBD /* NCRenameFile.swift in Sources */,
-				F7F4F0F727ECDBA4008676F9 /* NCSubtitles.swift in Sources */,
 				F71CD6CA2930D7B1006C95C1 /* NCApplicationHandle.swift in Sources */,
 				F790110E21415BF600D7B136 /* NCViewerRichdocument.swift in Sources */,
 				F78ACD4021903CC20088454D /* NCGridCell.swift in Sources */,
@@ -3241,7 +3221,6 @@
 				AF4BF61927562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */,
 				F78A18B623CDD07D00F681F3 /* NCViewerRichWorkspaceWebView.swift in Sources */,
 				AFA2AC8527849604008E1EA7 /* NCActivityCommentView.swift in Sources */,
-				F716B75F26F09DF600D37EFC /* NCKTVHTTPCache.swift in Sources */,
 				AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */,
 				AF36077127BFA4E8001A243D /* ParallelWorker.swift in Sources */,
 				F75A9EE623796C6F0044CFCE /* NCNetworking.swift in Sources */,
@@ -3282,7 +3261,6 @@
 				AF2D7C7C2742556F00ADF566 /* NCShareLinkCell.swift in Sources */,
 				F7E41316294A19B300839300 /* UIView+Extension.swift in Sources */,
 				F7C30E00291BD2610017149B /* NCNetworkingE2EERename.swift in Sources */,
-				F7F4F0F927ECDBA4008676F9 /* NCSubtitlePlayer.swift in Sources */,
 				F7651A8B23A2A3F2001403D2 /* NCCreateFormUploadDocuments.swift in Sources */,
 				F74AF3A4247FB6AE00AC767B /* NCUtilityFileSystem.swift in Sources */,
 				F7239871253D86B600257F49 /* NCEmptyDataSet.swift in Sources */,

+ 0 - 1
iOSClient/AppDelegate.swift

@@ -580,7 +580,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         if serverVersionMajor > 0 {
             NextcloudKit.shared.setup(nextcloudVersion: serverVersionMajor)
         }
-        NCKTVHTTPCache.shared.restartProxy(user: user, password: password)
 
         DispatchQueue.main.async {
             if UIApplication.shared.applicationState != .background && accountTestBackup != accountTest {

+ 7 - 34
iOSClient/Data/NCManageDatabase+Video.swift

@@ -28,9 +28,8 @@ import NextcloudKit
 class tableVideo: Object {
 
     @objc dynamic var account = ""
-    @objc dynamic var duration: Int64 = 0
     @objc dynamic var ocId = ""
-    @objc dynamic var time: Int64 = 0
+    @objc dynamic var position: Float = 0
     @objc dynamic var codecNameVideo: String?
     @objc dynamic var codecNameAudio: String?
     @objc dynamic var codecAudioChannelLayout: String?
@@ -45,7 +44,7 @@ class tableVideo: Object {
 
 extension NCManageDatabase {
 
-    func addVideoTime(metadata: tableMetadata, time: CMTime?, durationTime: CMTime?) {
+    func addVideo(metadata: tableMetadata, position: Float) {
 
         if metadata.livePhoto { return }
         let realm = try! Realm()
@@ -54,12 +53,7 @@ extension NCManageDatabase {
             try realm.write {
                 if let result = realm.objects(tableVideo.self).filter("account == %@ AND ocId == %@", metadata.account, metadata.ocId).first {
 
-                    if let durationTime = durationTime {
-                        result.duration = durationTime.convertScale(1000, method: .default).value
-                    }
-                    if let time = time {
-                        result.time = time.convertScale(1000, method: .default).value
-                    }
+                    result.position = position
                     realm.add(result, update: .all)
 
                 } else {
@@ -67,13 +61,8 @@ extension NCManageDatabase {
                     let addObject = tableVideo()
 
                     addObject.account = metadata.account
-                    if let durationTime = durationTime {
-                        addObject.duration = durationTime.convertScale(1000, method: .default).value
-                    }
                     addObject.ocId = metadata.ocId
-                    if let time = time {
-                        addObject.time = time.convertScale(1000, method: .default).value
-                    }
+                    addObject.position = position
                     realm.add(addObject, update: .all)
                 }
             }
@@ -125,22 +114,7 @@ extension NCManageDatabase {
         return tableVideo.init(value: result)
     }
 
-    func getVideoDurationTime(metadata: tableMetadata?) -> CMTime? {
-        guard let metadata = metadata else { return nil }
-
-        if metadata.livePhoto { return nil }
-        let realm = try! Realm()
-
-        guard let result = realm.objects(tableVideo.self).filter("account == %@ AND ocId == %@", metadata.account, metadata.ocId).first else {
-            return nil
-        }
-
-        if result.duration == 0 { return nil }
-        let duration = CMTimeMake(value: result.duration, timescale: 1000)
-        return duration
-    }
-
-    func getVideoTime(metadata: tableMetadata) -> CMTime? {
+    func getVideoPosition(metadata: tableMetadata) -> Float? {
 
         if metadata.livePhoto { return nil }
         let realm = try! Realm()
@@ -149,9 +123,8 @@ extension NCManageDatabase {
             return nil
         }
 
-        if result.time == 0 { return nil }
-        let time = CMTimeMake(value: result.time, timescale: 1000)
-        return time
+        if result.position == 0 { return nil }
+        return result.position
     }
 
     func deleteVideo(metadata: tableMetadata) {

+ 4 - 4
iOSClient/Menu/NCViewer+Menu.swift

@@ -206,13 +206,13 @@ extension NCViewer {
         }
 
         //
-        // DOWNLOAD IMAGE MAX RESOLUTION
+        // DOWNLOAD LOCALLY
         //
-        if !webView, metadata.session.isEmpty, metadata.classFile == NKCommon.TypeClassFile.image.rawValue, !CCUtility.fileProviderStorageExists(metadata) {
+        if !webView, metadata.session.isEmpty, !CCUtility.fileProviderStorageExists(metadata) {
             actions.append(
                 NCMenuAction(
-                    title: NSLocalizedString("_download_image_max_", comment: ""),
-                    icon: NCUtility.shared.loadImage(named: "square.and.arrow.down"),
+                    title: NSLocalizedString("_download_locally_", comment: ""),
+                    icon: NCUtility.shared.loadImage(named: "icloud.and.arrow.down"),
                     action: { _ in
                         NCNetworking.shared.download(metadata: metadata, selector: "") { _, _ in }
                     }

+ 0 - 4
iOSClient/NCGlobal.swift

@@ -367,10 +367,6 @@ class NCGlobal: NSObject {
     let notificationCenterShowPlayerToolBar                     = "showPlayerToolBar"               // userInfo: ocId, enableTimerAutoHide
     let notificationCenterOpenMediaDetail                       = "openMediaDetail"                 // userInfo: ocId
 
-    let notificationCenterReloadMediaPage                       = "reloadMediaPage"
-    let notificationCenterPlayMedia                             = "playMedia"
-    let notificationCenterPauseMedia                            = "pauseMedia"
-
     let notificationCenterDismissScanDocument                   = "dismissScanDocument"
     let notificationCenterDismissUploadAssets                   = "dismissUploadAssets"
 

+ 1 - 1
iOSClient/Settings/Acknowledgements.rtf

@@ -75,7 +75,7 @@ Copyright (c) Xmartlabs ( http://xmartlabs.com )\
 __________________________________\
 \
 
-\f1\b KTVHTTPCache
+\f1\b VLC
 \f0\b0 \
 \
 MIT License\

+ 0 - 2
iOSClient/Settings/CCAdvanced.m

@@ -405,8 +405,6 @@
 
     [CCUtility removeDocumentsDirectory];
     [CCUtility removeTemporaryDirectory];
-
-    [[NCKTVHTTPCache shared] deleteAllCache];
     
     [CCUtility createDirectoryStandard];
 

+ 1 - 1
iOSClient/Supporting Files/en.lproj/Localizable.strings

@@ -134,7 +134,7 @@
 "_of_"                      = "of";
 "_internal_modify_"         = "Edit with internal editor";
 "_database_corrupt_"        = "Oops something went wrong, please enter your credentials but don't worry, your files have remained secure";
-"_download_image_max_"      = "Download the image in full resolution";
+"_download_locally_"        = "Download locally";
 "_livephoto_save_"          = "Save Live Photo to photo album";
 "_livephoto_save_error_"    = "Error during save of Live Photo";
 "_file_conflict_num_"       = "file conflict";

+ 2 - 5
iOSClient/Utility/CCUtility.h

@@ -166,8 +166,8 @@
 + (NSInteger)getLogLevel;
 + (void)setLogLevel:(NSInteger)value;
 
-+ (BOOL)getAudioMute;
-+ (void)setAudioMute:(BOOL)set;
++ (NSInteger)getAudioVolume;
++ (void)setAudioVolume:(NSInteger)volume;
 
 + (BOOL)getAccountRequest;
 + (void)setAccountRequest:(BOOL)set;
@@ -184,9 +184,6 @@
 + (BOOL)getRemovePhotoCameraRoll;
 + (void)setRemovePhotoCameraRoll:(BOOL)set;
 
-+ (BOOL)getPlayerPlay;
-+ (void)setPlayerPlay:(BOOL)set;
-
 // ===== Varius =====
 
 + (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL;

+ 11 - 16
iOSClient/Utility/CCUtility.m

@@ -656,15 +656,21 @@
     [UICKeyChainStore setString:valueString forKey:@"logLevel" service:NCGlobal.shared.serviceShareKeyChain];
 }
 
-+ (BOOL)getAudioMute
++ (NSInteger)getAudioVolume
 {
-    return [[UICKeyChainStore stringForKey:@"audioMute" service:NCGlobal.shared.serviceShareKeyChain] boolValue];
+    NSString *volume = [UICKeyChainStore stringForKey:@"audioVolume" service:NCGlobal.shared.serviceShareKeyChain];
+
+    if (volume == nil) {
+        return 100;
+    } else {
+        return [volume integerValue];
+    }
 }
 
-+ (void)setAudioMute:(BOOL)set
++ (void)setAudioVolume:(NSInteger)volume
 {
-    NSString *sSet = (set) ? @"true" : @"false";
-    [UICKeyChainStore setString:sSet forKey:@"audioMute" service:NCGlobal.shared.serviceShareKeyChain];
+    NSString *volumeString = [@(volume) stringValue];
+    [UICKeyChainStore setString:volumeString forKey:@"audioVolume" service:NCGlobal.shared.serviceShareKeyChain];
 }
 
 + (BOOL)getAccountRequest
@@ -735,17 +741,6 @@
     [UICKeyChainStore setString:sSet forKey:@"removePhotoCameraRoll" service:NCGlobal.shared.serviceShareKeyChain];
 }
 
-+ (BOOL)getPlayerPlay
-{
-    return [[UICKeyChainStore stringForKey:@"playerPlay" service:NCGlobal.shared.serviceShareKeyChain] boolValue];
-}
-
-+ (void)setPlayerPlay:(BOOL)set
-{
-    NSString *sSet = (set) ? @"true" : @"false";
-    [UICKeyChainStore setString:sSet forKey:@"playerPlay" service:NCGlobal.shared.serviceShareKeyChain];
-}
-
 #pragma --------------------------------------------------------------------------------------------
 #pragma mark ===== Various =====
 #pragma --------------------------------------------------------------------------------------------

+ 0 - 2
iOSClient/Utility/NCUtility.swift

@@ -29,7 +29,6 @@ import CoreMedia
 import Photos
 
 #if !EXTENSION
-import KTVHTTPCache
 import SVGKit
 #endif
 
@@ -213,7 +212,6 @@ class NCUtility: NSObject {
 
         URLCache.shared.memoryCapacity = 0
         URLCache.shared.diskCapacity = 0
-        KTVHTTPCache.cacheDeleteAllCaches()
 
         NCManageDatabase.shared.clearDatabase(account: nil, removeAccount: true)
 

+ 0 - 148
iOSClient/Viewer/NCViewerMedia/NCPlayer/NCKTVHTTPCache.swift

@@ -1,148 +0,0 @@
-//
-//  NCKTVHTTPCache.swift
-//  Nextcloud
-//
-//  Created by Marino Faggiana on 28/10/2020.
-//  Copyright © 2020 Marino Faggiana. All rights reserved.
-//
-//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
-//
-//  This program is free software: you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License as published by
-//  the Free Software Foundation, either version 3 of the License, or
-//  (at your option) any later version.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-//
-//  You should have received a copy of the GNU General Public License
-//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-
-import UIKit
-import KTVHTTPCache
-
-class NCKTVHTTPCache: NSObject {
-    @objc static let shared: NCKTVHTTPCache = {
-        let instance = NCKTVHTTPCache()
-        instance.setupHTTPCache()
-        return instance
-    }()
-
-    func getVideoURL(metadata: tableMetadata) -> (url: URL?, isProxy: Bool) {
-
-        if CCUtility.fileProviderStorageExists(metadata) || metadata.isDirectoryE2EE {
-
-            return (URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)), false)
-
-        } else {
-
-            guard let stringURL = (metadata.serverUrl + "/" + metadata.fileName).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return (nil, false) }
-
-            return (NCKTVHTTPCache.shared.getProxyURL(stringURL: stringURL), true)
-        }
-    }
-
-    func restartProxy(user: String, password: String) {
-
-        if KTVHTTPCache.proxyIsRunning() {
-            KTVHTTPCache.proxyStop()
-        }
-
-        startProxy(user: user, password: password)
-    }
-
-    private func startProxy(user: String, password: String) {
-
-        guard let authData = (user + ":" + password).data(using: .utf8) else { return }
-
-        let authValue = "Basic " + authData.base64EncodedString(options: [])
-        KTVHTTPCache.downloadSetAdditionalHeaders(["Authorization": authValue, "User-Agent": CCUtility.getUserAgent()])
-
-        if !KTVHTTPCache.proxyIsRunning() {
-            do {
-                try KTVHTTPCache.proxyStart()
-            } catch let error {
-                print("Proxy Start error : \(error)")
-            }
-        }
-    }
-
-    private func stopProxy() {
-
-        if KTVHTTPCache.proxyIsRunning() {
-            KTVHTTPCache.proxyStop()
-        }
-    }
-
-    func getProxyURL(stringURL: String) -> URL {
-
-        return KTVHTTPCache.proxyURL(withOriginalURL: URL(string: stringURL))
-    }
-
-    func getCacheCompleteFileURL(videoURL: URL?) -> URL? {
-
-        return KTVHTTPCache.cacheCompleteFileURL(with: videoURL)
-    }
-
-    func deleteCache(videoURL: URL?) {
-
-        KTVHTTPCache.cacheDelete(with: videoURL)
-    }
-
-    @objc func deleteAllCache() {
-
-        KTVHTTPCache.cacheDeleteAllCaches()
-    }
-
-    func saveCache(metadata: tableMetadata) {
-
-        if !CCUtility.fileProviderStorageExists(metadata) {
-
-            guard let stringURL = (metadata.serverUrl + "/" + metadata.fileName).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return }
-
-            let videoURL = URL(string: stringURL)
-            guard let url = KTVHTTPCache.cacheCompleteFileURL(with: videoURL) else { return }
-
-            CCUtility.copyFile(atPath: url.path, toPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))
-            NCManageDatabase.shared.addLocalFile(metadata: metadata)
-            KTVHTTPCache.cacheDelete(with: videoURL)
-
-            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSource, userInfo: ["serverUrl": metadata.serverUrl])
-        }
-    }
-
-    func getDownloadStatusCode(metadata: tableMetadata) -> Int {
-
-        guard let stringURL = (metadata.serverUrl + "/" + metadata.fileName).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return 0 }
-        let url = URL(string: stringURL)
-        return KTVHTTPCache.downloadStatusCode(url)
-    }
-
-    private func setupHTTPCache() {
-
-        KTVHTTPCache.cacheSetMaxCacheLength(NCGlobal.shared.maxHTTPCache)
-
-        if ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] != nil {
-            KTVHTTPCache.logSetConsoleLogEnable(true)
-        }
-
-        do {
-            try KTVHTTPCache.proxyStart()
-        } catch let error {
-            print("Proxy Start error : \(error)")
-        }
-
-        KTVHTTPCache.encodeSetURLConverter { url -> URL? in
-            print("URL Filter received URL : " + String(describing: url))
-            return url
-        }
-
-        KTVHTTPCache.downloadSetUnacceptableContentTypeDisposer { url, contentType -> Bool in
-            print("Unsupport Content-Type Filter received URL : " + String(describing: url) + " " + String(describing: contentType))
-            return false
-        }
-    }
-}

+ 174 - 353
iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayer.swift

@@ -24,452 +24,273 @@
 import Foundation
 import NextcloudKit
 import UIKit
-import AVFoundation
-import MediaPlayer
-import JGProgressHUD
-import Alamofire
+import MobileVLCKit
 
 class NCPlayer: NSObject {
 
     internal let appDelegate = UIApplication.shared.delegate as! AppDelegate
-    internal var url: URL
+    internal var url: URL?
+    internal var player: VLCMediaPlayer?
+    internal var thumbnailer: VLCMediaThumbnailer?
+    internal var metadata: tableMetadata
+    internal var singleTapGestureRecognizer: UITapGestureRecognizer!
+    internal var width: Int64?
+    internal var height: Int64?
+    internal let fileNamePreviewLocalPath: String
+    internal let fileNameIconLocalPath: String
+
     internal weak var playerToolBar: NCPlayerToolBar?
-    internal weak var viewController: UIViewController?
-    internal var autoPlay: Bool
-    internal var isProxy: Bool
-    internal var isStartPlayer: Bool
-    internal var isStartObserver: Bool
-    internal var subtitleUrls: [URL] = []
-    internal var currentSubtitle: URL?
-
-    private weak var imageVideoContainer: imageVideoContainerView?
-    private weak var detailView: NCViewerMediaDetailView?
-    private var observerAVPlayerItemDidPlayToEndTime: Any?
-    private var observerAVPlayertTime: Any?
-
-    var kvoPlayerObserver: NSKeyValueObservation?
-    var player: AVPlayer?
-    var durationTime: CMTime = .zero
-    var metadata: tableMetadata
-    var videoLayer: AVPlayerLayer?
+    internal weak var viewerMediaPage: NCViewerMediaPage?
+
+    weak var imageVideoContainer: imageVideoContainerView?
 
     // MARK: - View Life Cycle
 
-    init(url: URL, autoPlay: Bool, isProxy: Bool, imageVideoContainer: imageVideoContainerView, playerToolBar: NCPlayerToolBar?, metadata: tableMetadata, detailView: NCViewerMediaDetailView?, viewController: UIViewController) {
+    init(imageVideoContainer: imageVideoContainerView, playerToolBar: NCPlayerToolBar?, metadata: tableMetadata, viewerMediaPage: NCViewerMediaPage?) {
 
-        self.url = url
-        self.autoPlay = autoPlay
-        self.isProxy = isProxy
-        self.isStartPlayer = false
-        self.isStartObserver = false
         self.imageVideoContainer = imageVideoContainer
         self.playerToolBar = playerToolBar
         self.metadata = metadata
-        self.detailView = detailView
-        self.viewController = viewController
+        self.viewerMediaPage = viewerMediaPage
 
-        super.init()
+        fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)!
+        fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)!
 
-        do {
-            try AVAudioSession.sharedInstance().setCategory(.playback)
-            try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.none)
-            try AVAudioSession.sharedInstance().setActive(true)
-        } catch {
-            print(error)
-        }
+        super.init()
     }
 
     deinit {
 
         print("deinit NCPlayer with ocId \(metadata.ocId)")
-        deactivateObserver()
+        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidEnterBackground), object: nil)
     }
 
-    func openAVPlayer() {
+    func openAVPlayer(url: URL) {
 
-#if MFFFLIB
-        MFFF.shared.setDelegate = self
-        MFFF.shared.dismissMessage()
-        NotificationCenter.default.addObserver(self, selector: #selector(convertVideoDidFinish(_:)), name: NSNotification.Name(rawValue: self.metadata.ocId), object: nil)
+        let userAgent = CCUtility.getUserAgent()!
+        var position: Float = 0
 
-        if CCUtility.fileProviderStorageExists(metadata) {
-            self.url = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: NCGlobal.shared.fileNameVideoEncoded))
-            self.isProxy = false
-        }
-        if MFFF.shared.existsMFFFSession(url: URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))) {
-            return
-        }
-#endif
-
-        // Check already started
-        if isStartPlayer {
-            if !isStartObserver {
-                print("Play already started - URL: \(self.url)")
-                activateObserver()
-                playerToolBar?.show()
-            }
-            return
-        }
-
-        print("Play URL: \(self.url)")
-        player = AVPlayer(url: self.url)
-        playerToolBar?.show()
-        playerToolBar?.setMetadata(self.metadata)
-#if MFFFLIB
-        setUpForSubtitle()
-#endif
-
-        if metadata.livePhoto {
-            player?.isMuted = false
-        } else if metadata.classFile == NKCommon.TypeClassFile.audio.rawValue {
-            player?.isMuted = CCUtility.getAudioMute()
-        } else {
-            player?.isMuted = CCUtility.getAudioMute()
-            if let time = NCManageDatabase.shared.getVideoTime(metadata: metadata) {
-                player?.seek(to: time)
-            }
-        }
+        self.url = url
+        self.singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didSingleTapWith(gestureRecognizer:)))
 
-        let observerAVPlayertStatus = self.player?.currentItem?.observe(\.status, options: [.new,.initial]) { player, change in
+        print("Play URL: \(url)")
+        player = VLCMediaPlayer()
+        player?.media = VLCMedia(url: url)
+        player?.delegate = self
 
-            if let player = self.player,
-               let playerItem = player.currentItem,
-               let object = player.currentItem,
-               playerItem === object,
-               self.viewController != nil {
+        // player?.media?.addOption("--network-caching=500")
+        player?.media?.addOption(":http-user-agent=\(userAgent)")
 
-                if self.isStartPlayer {
-                    return
-                }
-                if (playerItem.status == .readyToPlay || playerItem.status == .failed) {
-                    print("Player ready")
-                    self.startPlayer()
-                } else {
-                    print("Player not ready")
-                }
-            }
+        if let result = NCManageDatabase.shared.getVideoPosition(metadata: metadata) {
+            position = result
+            player?.position = position
         }
 
-        if let observerAVPlayertStatus = observerAVPlayertStatus{
-            kvoPlayerObserver = observerAVPlayertStatus
+        player?.drawable = imageVideoContainer
+        if let view = player?.drawable as? UIView {
+            view.isUserInteractionEnabled = true
+            view.addGestureRecognizer(singleTapGestureRecognizer)
         }
-    }
-
-    func startPlayer() {
 
-        player?.currentItem?.asset.loadValuesAsynchronously(forKeys: ["playable"], completionHandler: {
+        playerToolBar?.setBarPlayer(ncplayer: self, position: position, metadata: metadata)
 
-            var error: NSError? = nil
-            let status = self.player?.currentItem?.asset.statusOfValue(forKey: "playable", error: &error) ?? .unknown
-
-            DispatchQueue.main.async {
-
-                switch status {
-                case .loaded:
-
-                    self.durationTime = self.player?.currentItem?.asset.duration ?? .zero
-                    NCManageDatabase.shared.addVideoTime(metadata: self.metadata, time: nil, durationTime: self.durationTime)
-
-                    self.videoLayer = AVPlayerLayer(player: self.player)
-                    self.videoLayer!.frame = self.imageVideoContainer?.bounds ?? .zero
-                    self.videoLayer!.videoGravity = .resizeAspect
+        if let media = player?.media {
+            thumbnailer = VLCMediaThumbnailer(media: media, andDelegate: self)
+        }
 
-                    if self.metadata.classFile == NKCommon.TypeClassFile.video.rawValue {
-                        self.imageVideoContainer?.layer.addSublayer(self.videoLayer!)
-                        self.imageVideoContainer?.playerLayer = self.videoLayer
-                        self.imageVideoContainer?.metadata = self.metadata
-                        self.imageVideoContainer?.image = self.imageVideoContainer?.image?.image(alpha: 0)
-                    }
+        NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidEnterBackground), object: nil)
+    }
 
-                    self.playerToolBar?.setBarPlayer(ncplayer: self)
-                    self.generatorImagePreview()
-                    if !(self.detailView?.isShow() ?? false) {
-                        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterShowPlayerToolBar, userInfo: ["ocId":self.metadata.ocId, "enableTimerAutoHide": false])
-                    }
-                    self.activateObserver()
-                    if self.autoPlay || CCUtility.getPlayerPlay() {
-                        self.player?.play()
-                    }
-                    self.isStartPlayer = true
-                    break
-
-                case .failed:
-
-                    self.playerToolBar?.hide()
-                    if self.isProxy && NCKTVHTTPCache.shared.getDownloadStatusCode(metadata: self.metadata) == 200 {
-                        let alertController = UIAlertController(title: NSLocalizedString("_error_", value: "Error", comment: ""), message: NSLocalizedString("_video_not_streamed_", comment: ""), preferredStyle: .alert)
-                        alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", value: "Yes", comment: ""), style: .default, handler: { _ in
-                            self.downloadVideo()
-                        }))
-                        alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", value: "No", comment: ""), style: .default, handler: { _ in }))
-                        self.viewController?.present(alertController, animated: true)
-                    } else if self.metadata.isDirectoryE2EE {
-                        let alertController = UIAlertController(title: NSLocalizedString("_info_", value: "Info", comment: ""), message: NSLocalizedString("_video_not_streamed_e2ee_", comment: ""), preferredStyle: .alert)
-                        alertController.addAction(UIAlertAction(title: NSLocalizedString("_yes_", value: "Yes", comment: ""), style: .default, handler: { _ in
-                            self.downloadVideo(isEncrypted: true)
-                        }))
-                        alertController.addAction(UIAlertAction(title: NSLocalizedString("_no_", value: "No", comment: ""), style: .default, handler: { _ in }))
-                        self.viewController?.present(alertController, animated: true)
-                    } else {
-#if MFFFLIB
-                        if error?.code == AVError.Code.fileFormatNotRecognized.rawValue {
-                            self.convertVideo(withAlert: true)
-                            break
-                        }
-#endif
-                        if let title = error?.localizedDescription, let description = error?.localizedFailureReason {
-                            let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: description)
-                            NCContentPresenter.shared.messageNotification(title, error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
-                        } else {
-                            let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_error_something_wrong_")
-                            NCContentPresenter.shared.showError(error: error, priority: .max)
-                        }
-                    }
-                    break
+    // MARK: - UIGestureRecognizerDelegate
 
-                case .cancelled:
-                    break
+    @objc func didSingleTapWith(gestureRecognizer: UITapGestureRecognizer) {
 
-                default:
-                    break
-                }
-            }
-        })
+        viewerMediaPage?.didSingleTapWith(gestureRecognizer: gestureRecognizer)
     }
 
-    func activateObserver() {
-        print("activating Observer ocId \(metadata.ocId)")
+    // MARK: - NotificationCenter
 
-        observerAVPlayerItemDidPlayToEndTime = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) {  [weak self] notification in
+    @objc func applicationDidEnterBackground(_ notification: NSNotification) {
 
-            guard let self = self else {
-                return
-            }
+        if metadata.classFile == NKCommon.TypeClassFile.video.rawValue {
+            playerStop()
+        }
+    }
 
-            if let item = notification.object as? AVPlayerItem, let currentItem = self.player?.currentItem, item == currentItem {
+    // MARK: -
 
-                NCKTVHTTPCache.shared.saveCache(metadata: self.metadata)
+    func isPlay() -> Bool {
 
-                self.videoSeek(time: .zero)
+        return player?.isPlaying ?? false
+    }
 
-                if !(self.detailView?.isShow() ?? false) {
-                    NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterShowPlayerToolBar, userInfo: ["ocId": self.metadata.ocId, "enableTimerAutoHide": false])
-                }
+    func playerPlay() {
 
-                self.playerToolBar?.updateToolBar()
-            }
+        playerToolBar?.playbackSliderEvent = .began
+        player?.play()
+        playerToolBar?.playButtonPause()
+        
+        if let position = NCManageDatabase.shared.getVideoPosition(metadata: metadata) {
+            player?.position = position
+            playerToolBar?.playbackSliderEvent = .moved
         }
 
-        // Evey 1 second update toolbar
-        observerAVPlayertTime = player?.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: .main, using: { [weak self] _ in
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+            self.playerToolBar?.playbackSliderEvent = .ended
+        }
+    }
 
-            guard let self = self else {
-                return
-            }
+    @objc func playerStop() {
 
-            if self.player?.currentItem?.status == .readyToPlay {
-                self.playerToolBar?.updateToolBar()
-            }
-        })
+        savePosition()
+        player?.stop()
+        playerToolBar?.playButtonPlay()
+    }
 
-        NotificationCenter.default.addObserver(self, selector: #selector(generatorImagePreview), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationWillResignActive), object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidEnterBackground), object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil)
+    @objc func playerPause() {
 
-        NotificationCenter.default.addObserver(self, selector: #selector(playerPause), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterPauseMedia), object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(playerPlay), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterPlayMedia), object: nil)
+        savePosition()
+        player?.pause()
+        playerToolBar?.playButtonPlay()
+    }
 
-        if let player = self.player {
-            NotificationCenter.default.addObserver(self, selector: #selector(playerStalled), name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: player.currentItem)
-        }
+    func playerPosition(_ position: Float) {
 
-        isStartObserver = true
+        NCManageDatabase.shared.addVideo(metadata: metadata, position: position)
+        player?.position = position
     }
 
-    func deactivateObserver() {
+    func savePosition() {
 
-        print("deactivating Observer ocId \(metadata.ocId)")
+        guard let position = player?.position, metadata.classFile == NKCommon.TypeClassFile.video.rawValue, isPlay() else { return }
 
-        if isPlay() {
-            playerPause()
+        if let width = width, let height = height {
+            player?.saveVideoSnapshot(at: fileNamePreviewLocalPath, withWidth: Int32(width), andHeight: Int32(height))
         }
-        
-        self.kvoPlayerObserver?.invalidate()
-        self.kvoPlayerObserver = nil
 
-        if let observerAVPlayerItemDidPlayToEndTime = self.observerAVPlayerItemDidPlayToEndTime {
-            NotificationCenter.default.removeObserver(observerAVPlayerItemDidPlayToEndTime)
-        }
-        observerAVPlayerItemDidPlayToEndTime = nil
+        NCManageDatabase.shared.addVideo(metadata: metadata, position: position)
+    }
 
-        if let observerAVPlayertTime = self.observerAVPlayertTime,
-           let player = player {
-            player.removeTimeObserver(observerAVPlayertTime)
-        }
-        observerAVPlayertTime = nil
+    func setVolumeAudio(_ volume: Int32) {
 
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationWillResignActive), object: nil)
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidEnterBackground), object: nil)
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil)
+        player?.audio?.volume = volume
+    }
 
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: nil)
+    func jumpForward(_ seconds: Int32) {
 
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterPauseMedia), object: nil)
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterPlayMedia), object: nil)
-        
-        isStartObserver = false
+        player?.jumpForward(seconds)
     }
 
-    // MARK: - NotificationCenter
+    func jumpBackward(_ seconds: Int32) {
 
-    @objc func applicationDidEnterBackground(_ notification: NSNotification) {
+        player?.jumpBackward(seconds)
+    }
+}
 
-        if metadata.classFile == NKCommon.TypeClassFile.video.rawValue, let playerToolBar = self.playerToolBar {
-            if !playerToolBar.isPictureInPictureActive() {
-                playerPause()
+extension NCPlayer: VLCMediaPlayerDelegate {
+
+    func mediaPlayerStateChanged(_ aNotification: Notification) {
+        guard let player = self.player else { return }
+
+        switch player.state {
+        case .stopped:
+            print("Played mode: STOPPED")
+            break
+        case .opening:
+            print("Played mode: OPENING")
+            break
+        case .buffering:
+            print("Played mode: BUFFERING")
+            break
+        case .ended:
+            if let url = self.url {
+                NCManageDatabase.shared.addVideo(metadata: metadata, position: 0)
+                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterShowPlayerToolBar, userInfo: ["ocId": self.metadata.ocId, "enableTimerAutoHide": false])
+                self.thumbnailer?.fetchThumbnail()
+                self.openAVPlayer(url: url)
             }
+            print("Played mode: ENDED")
+            break
+        case .error:
+            playerToolBar?.disableAllControl()
+            let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_error_something_wrong_")
+            NCContentPresenter.shared.showError(error: error, priority: .max)
+            print("Played mode: ERROR")
+            break
+        case .playing:
+            if let tracksInformation = player.media?.tracksInformation {
+                for case let track as [String:Any] in tracksInformation {
+                    if track["type"] as? String == "video" {
+                        width = track["width"] as? Int64
+                        height = track["height"] as? Int64
+                    }
+                }
+            }
+            print("Played mode: PLAYING")
+            break
+        case .paused:
+            print("Played mode: PAUSED")
+            break
+        default: break
         }
     }
 
-    @objc func applicationDidBecomeActive(_ notification: NSNotification) {
+    func mediaPlayerTimeChanged(_ aNotification: Notification) {
 
-        playerToolBar?.updateToolBar()
+        playerToolBar?.update()
     }
 
-    // MARK: -
-
-    func isPlay() -> Bool {
-
-        if player?.rate == 1 { return true } else { return false }
+    func mediaPlayerTitleChanged(_ aNotification: Notification) {
+        // Handle other states...
     }
 
-    @objc func playerStalled() {
-
-        print("current player \(String(describing: player)) stalled.\nCalling playerPlay()")
-        playerPlay()
+    func mediaPlayerChapterChanged(_ aNotification: Notification) {
+        // Handle other states...
     }
 
-    @objc func playerPlay() {
-
-        player?.play()
-        playerToolBar?.updateToolBar()
+    func mediaPlayerLoudnessChanged(_ aNotification: Notification) {
+        // Handle other states...
     }
 
-    @objc func playerPause() {
-
-        player?.pause()
-        playerToolBar?.updateToolBar()
-
-        if let playerToolBar = self.playerToolBar, playerToolBar.isPictureInPictureActive() {
-            playerToolBar.pictureInPictureController?.stopPictureInPicture()
+    func mediaPlayerSnapshot(_ aNotification: Notification) {
+        
+        if let data = NSData(contentsOfFile: fileNamePreviewLocalPath),
+           let image = UIImage(data: data as Data),
+           let image = image.resizeImage(size: CGSize(width: NCGlobal.shared.sizeIcon, height: NCGlobal.shared.sizeIcon)),
+           let data = image.jpegData(compressionQuality: 0.5) {
+            try? data.write(to: URL(fileURLWithPath: fileNameIconLocalPath))
         }
+        print("Snapshot saved on \(fileNameIconLocalPath)")
     }
 
-    func videoSeek(time: CMTime) {
-
-        player?.seek(to: time)
-        saveTime(time)
+    func mediaPlayerStartedRecording(_ player: VLCMediaPlayer) {
+        // Handle other states...
     }
 
-    func saveTime(_ time: CMTime) {
-
-        if metadata.classFile == NKCommon.TypeClassFile.audio.rawValue { return }
-
-        NCManageDatabase.shared.addVideoTime(metadata: metadata, time: time, durationTime: nil)
-        generatorImagePreview()
+    func mediaPlayer(_ player: VLCMediaPlayer, recordingStoppedAtPath path: String) {
+        // Handle other states...
     }
+}
 
-    func saveCurrentTime() {
+extension NCPlayer: VLCMediaThumbnailerDelegate {
 
-        if let player = self.player {
-            saveTime(player.currentTime())
-        }
-    }
-
-    @objc func generatorImagePreview() {
+    func mediaThumbnailerDidTimeOut(_ mediaThumbnailer: VLCMediaThumbnailer) { }
 
-        guard let time = player?.currentTime(), !metadata.livePhoto, metadata.classFile != NKCommon.TypeClassFile.audio.rawValue  else { return }
+    func mediaThumbnailer(_ mediaThumbnailer: VLCMediaThumbnailer, didFinishThumbnail thumbnail: CGImage) {
 
         var image: UIImage?
 
-        if let asset = player?.currentItem?.asset {
-
-            do {
-                let fileNamePreviewLocalPath = CCUtility.getDirectoryProviderStoragePreviewOcId(metadata.ocId, etag: metadata.etag)!
-                let fileNameIconLocalPath = CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)!
-                let imageGenerator = AVAssetImageGenerator(asset: asset)
-                imageGenerator.appliesPreferredTrackTransform = true
-                let cgImage = try imageGenerator.copyCGImage(at: time, actualTime: nil)
-                image = UIImage(cgImage: cgImage)
-                // Update Playing Info Center
-                let mediaItemPropertyTitle = MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPMediaItemPropertyTitle] as? String
-                if let image = image, mediaItemPropertyTitle == metadata.fileNameView {
-                    MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in
-                        return image
-                    }
-                }
-                // Preview
-                if let data = image?.jpegData(compressionQuality: 0.5) {
-                    try data.write(to: URL(fileURLWithPath: fileNamePreviewLocalPath), options: .atomic)
-                }
-                // Icon
-                if let data = image?.jpegData(compressionQuality: 0.5) {
-                    try data.write(to: URL(fileURLWithPath: fileNameIconLocalPath), options: .atomic)
-                }
-            } catch let error as NSError {
-                print("GeneratorImagePreview localized error:")
-                print(error.localizedDescription)
+        do {
+            image = UIImage(cgImage: thumbnail)
+            if let data = image?.jpegData(compressionQuality: 0.5) {
+                try data.write(to: URL(fileURLWithPath: fileNamePreviewLocalPath), options: .atomic)
             }
-        }
-    }
-
-    internal func downloadVideo(isEncrypted: Bool = false, requiredConvert: Bool = false) {
-
-        guard let view = appDelegate.window?.rootViewController?.view else { return }
-        let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
-        let fileNameLocalPath = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)!
-        let hud = JGProgressHUD()
-        var downloadRequest: DownloadRequest?
-
-        hud.indicatorView = JGProgressHUDRingIndicatorView()
-        if let indicatorView = hud.indicatorView as? JGProgressHUDRingIndicatorView {
-            indicatorView.ringWidth = 1.5
-        }
-        hud.textLabel.text = NSLocalizedString(metadata.fileNameView, comment: "")
-        hud.detailTextLabel.text = NSLocalizedString("_tap_to_cancel_", comment: "")
-        hud.show(in: view)
-        hud.tapOnHUDViewBlock = { hud in
-            downloadRequest?.cancel()
-        }
-
-        NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath) { request in
-            downloadRequest = request
-        } taskHandler: { task in
-            // task
-        } progressHandler: { progress in
-            hud.progress = Float(progress.fractionCompleted)
-        } completionHandler: { _, _, _, _, _, afError, error in
-            if afError == nil {
-                NCManageDatabase.shared.addLocalFile(metadata: self.metadata)
-                if isEncrypted {
-                    if let result = NCManageDatabase.shared.getE2eEncryption(predicate: NSPredicate(format: "fileNameIdentifier == %@ AND serverUrl == %@", self.metadata.fileName, self.metadata.serverUrl)) {
-                        NCEndToEndEncryption.sharedManager()?.decryptFile(self.metadata.fileName, fileNameView: self.metadata.fileNameView, ocId: self.metadata.ocId, key: result.key, initializationVector: result.initializationVector, authenticationTag: result.authenticationTag)
-                    }
-                }
-                let urlVideo = NCKTVHTTPCache.shared.getVideoURL(metadata: self.metadata)
-                if let url = urlVideo.url {
-                    self.url = url
-                    self.isProxy = urlVideo.isProxy
-                    if requiredConvert {
-#if MFFFLIB
-                        self.convertVideo(withAlert: false)
-#endif
-                    } else {
-                        self.openAVPlayer()
-                    }
-                }
+            if let data = image?.jpegData(compressionQuality: 0.5) {
+                try data.write(to: URL(fileURLWithPath: fileNameIconLocalPath), options: .atomic)
             }
-            hud.dismiss()
+        } catch let error as NSError {
+            print("GeneratorImagePreview localized error:")
+            print(error.localizedDescription)
         }
     }
 }

+ 94 - 304
iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.swift

@@ -27,13 +27,12 @@ import CoreMedia
 import UIKit
 import AVKit
 import MediaPlayer
+import MobileVLCKit
 
 class NCPlayerToolBar: UIView {
 
     @IBOutlet weak var playerTopToolBarView: UIStackView!
     @IBOutlet weak var playerToolBarView: UIView!
-    @IBOutlet weak var pipButton: UIButton!
-    @IBOutlet weak var subtitleButton: UIButton!
     @IBOutlet weak var muteButton: UIButton!
     @IBOutlet weak var playButton: UIButton!
     @IBOutlet weak var forwardButton: UIButton!
@@ -47,29 +46,22 @@ class NCPlayerToolBar: UIView {
         case ended
         case moved
     }
+    var playbackSliderEvent: sliderEventType = .ended
 
-    var ncplayer: NCPlayer?
+    private var ncplayer: NCPlayer?
     private var metadata: tableMetadata?
     private var wasInPlay: Bool = false
-    private var playbackSliderEvent: sliderEventType = .ended
     private var timerAutoHide: Timer?
-
     private var timerAutoHideSeconds: Double {
         get {
             if NCUtility.shared.isSimulator() { // for test
-                return 15
+                return 150
             } else {
                 return 3.5
             }
         }
     }
 
-
-// NCUtility.shared.isSimulatorOrTestFlight()
-
-    var pictureInPictureController: AVPictureInPictureController?
-    weak var viewerMediaPage: NCViewerMediaPage?
-
     // MARK: - View Life Cycle
 
     override func awakeFromNib() {
@@ -91,39 +83,22 @@ class NCPlayerToolBar: UIView {
         playerToolBarView.layer.masksToBounds = true
         playerToolBarView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapToolBarWith(gestureRecognizer:))))
 
-        pipButton.setImage(NCUtility.shared.loadImage(named: "pip.enter", color: .lightGray), for: .normal)
-        pipButton.isEnabled = false
-
-        muteButton.setImage(NCUtility.shared.loadImage(named: "audioOff", color: .lightGray), for: .normal)
-        muteButton.isEnabled = false
-
-        subtitleButton.setImage(NCUtility.shared.loadImage(named: "captions.bubble", color: .white), for: .normal)
-        subtitleButton.isEnabled = true
-        subtitleButton.isHidden = true
-
         playbackSlider.value = 0
         playbackSlider.minimumValue = 0
-        playbackSlider.maximumValue = 0
+        playbackSlider.maximumValue = 1
         playbackSlider.isContinuous = true
         playbackSlider.tintColor = .lightGray
-        playbackSlider.isEnabled = false
 
-        labelCurrentTime.text = NCUtility.shared.stringFromTime(.zero)
-        labelCurrentTime.textColor = .lightGray
-        labelLeftTime.text = NCUtility.shared.stringFromTime(.zero)
-        labelLeftTime.textColor = .lightGray
+        labelCurrentTime.textColor = .white
+        labelLeftTime.textColor = .white
 
-        backButton.setImage(NCUtility.shared.loadImage(named: "gobackward.10", color: .lightGray), for: .normal)
-        backButton.isEnabled = false
+        muteButton.setImage(NCUtility.shared.loadImage(named: "audioOn", color: .white), for: .normal)
 
-        playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .lightGray, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: 30)), for: .normal)
-        playButton.isEnabled = false
+        playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: 30)), for: .normal)
 
-        forwardButton.setImage(NCUtility.shared.loadImage(named: "goforward.10", color: .lightGray), for: .normal)
-        forwardButton.isEnabled = false
+        backButton.setImage(NCUtility.shared.loadImage(named: "gobackward.10", color: .white), for: .normal)
 
-        NotificationCenter.default.addObserver(self, selector: #selector(handleInterruption), name: AVAudioSession.interruptionNotification, object: nil)
-        NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange), name: AVAudioSession.routeChangeNotification, object: nil)
+        forwardButton.setImage(NCUtility.shared.loadImage(named: "goforward.10", color: .white), for: .normal)
     }
 
     required init?(coder aDecoder: NSCoder) {
@@ -132,147 +107,66 @@ class NCPlayerToolBar: UIView {
 
     deinit {
         print("deinit NCPlayerToolBar")
-
-        NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil)
-        NotificationCenter.default.removeObserver(self, name: AVAudioSession.routeChangeNotification, object: nil)
     }
 
     // MARK: -
 
-    func setMetadata(_ metadata: tableMetadata) {
+    func setBarPlayer(ncplayer: NCPlayer, position: Float, metadata: tableMetadata) {
 
+        self.ncplayer = ncplayer
         self.metadata = metadata
-    }
 
-    func setBarPlayer(ncplayer: NCPlayer) {
+        playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: 30)), for: .normal)
+        MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 0
 
-        self.ncplayer = ncplayer
-
-        playbackSlider.value = 0
-        playbackSlider.minimumValue = 0
-        playbackSlider.maximumValue = Float(ncplayer.durationTime.seconds)
+        playbackSlider.value = position
         playbackSlider.addTarget(self, action: #selector(onSliderValChanged(slider:event:)), for: .valueChanged)
 
-        labelCurrentTime.text = NCUtility.shared.stringFromTime(.zero)
-        labelLeftTime.text = "-" + NCUtility.shared.stringFromTime(ncplayer.durationTime)
-
-        updateToolBar()
-    }
-
-    public func updateToolBar() {
-
-        guard let ncplayer = self.ncplayer else { return }
-
-        // MUTE
-        if let muteButton = muteButton {
-            if CCUtility.getAudioMute() {
-                muteButton.setImage(NCUtility.shared.loadImage(named: "audioOff", color: .white), for: .normal)
-            } else {
-                muteButton.setImage(NCUtility.shared.loadImage(named: "audioOn", color: .white), for: .normal)
-            }
-            muteButton.isEnabled = true
-        }
-
-        // PIP
-        if let pipButton = pipButton {
-            if metadata?.classFile == NKCommon.TypeClassFile.video.rawValue && AVPictureInPictureController.isPictureInPictureSupported() {
-                pipButton.setImage(NCUtility.shared.loadImage(named: "pip.enter", color: .white), for: .normal)
-                pipButton.isEnabled = true
-            } else {
-                pipButton.setImage(NCUtility.shared.loadImage(named: "pip.enter", color: .gray), for: .normal)
-                pipButton.isEnabled = false
-            }
-        }
-
-        // SLIDER TIME (START - END)
-        let time = (ncplayer.player?.currentTime() ?? .zero).convertScale(1000, method: .default)
-        playbackSlider.value = Float(time.seconds)
-        MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = time.seconds
-        playbackSlider.isEnabled = true
-        labelCurrentTime.text = NCUtility.shared.stringFromTime(time)
-        labelLeftTime.text = "-" + NCUtility.shared.stringFromTime(ncplayer.durationTime - time)
-
-        // BACK
-        backButton.setImage(NCUtility.shared.loadImage(named: "gobackward.10", color: .white), for: .normal)
-        backButton.isEnabled = true
+        labelCurrentTime.text = ncplayer.player?.time.stringValue
+        labelLeftTime.text = ncplayer.player?.remainingTime?.stringValue
 
-        // PLAY
-        if ncplayer.isPlay() {
-            MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 1
+        if CCUtility.getAudioVolume() == 0 {
+            ncplayer.setVolumeAudio(0)
+            muteButton.setImage(NCUtility.shared.loadImage(named: "audioOff", color: .white), for: .normal)
         } else {
-            MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 0
+            ncplayer.setVolumeAudio(100)
+            muteButton.setImage(NCUtility.shared.loadImage(named: "audioOn", color: .white), for: .normal)
         }
-        let namedPlay = ncplayer.isPlay() ? "pause.fill" : "play.fill"
-        playButton.setImage(NCUtility.shared.loadImage(named: namedPlay, color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: 30)), for: .normal)
-        playButton.isEnabled = true
 
-        // FORWARD
-        forwardButton.setImage(NCUtility.shared.loadImage(named: "goforward.10", color: .white), for: .normal)
-        forwardButton.isEnabled = true
+        show(enableTimerAutoHide: false)
     }
 
-    // MARK: Handle Notifications
-
-    @objc func handleRouteChange(notification: Notification) {
+    public func update() {
 
-        guard let userInfo = notification.userInfo, let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return }
+        guard let ncplayer = self.ncplayer,
+              let length = ncplayer.player?.media?.length.intValue,
+              let position = ncplayer.player?.position
+        else { return }
+        let positionInSecond = position * Float(length / 1000)
 
-        switch reason {
-        case .newDeviceAvailable:
-            let session = AVAudioSession.sharedInstance()
-            for output in session.currentRoute.outputs where output.portType == AVAudioSession.Port.headphones {
-                print("headphones connected")
-                ncplayer?.playerPlay()
-                startTimerAutoHide()
-                break
-            }
-        case .oldDeviceUnavailable:
-            if let previousRoute = userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
-                for output in previousRoute.outputs where output.portType == AVAudioSession.Port.headphones {
-                    print("headphones disconnected")
-                    ncplayer?.playerPause()
-                    ncplayer?.saveCurrentTime()
-                    break
-                }
-            }
-        default: ()
+        // SLIDER & TIME
+        if playbackSliderEvent == .ended {
+            playbackSlider.value = position
         }
+        labelCurrentTime.text = ncplayer.player?.time.stringValue
+        labelLeftTime.text = ncplayer.player?.remainingTime?.stringValue
+        MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPMediaItemPropertyPlaybackDuration] = length / 1000
+        MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyElapsedPlaybackTime] = positionInSecond
     }
 
-    @objc func handleInterruption(notification: Notification) {
-
-        guard let userInfo = notification.userInfo, let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt, let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return }
-
-        if type == .began {
-            print("Interruption began")
-        } else if type == .ended {
-            if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
-                let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
-                if options.contains(.shouldResume) {
-                    print("Interruption Ended - playback should resume")
-                    ncplayer?.playerPlay()
-                    startTimerAutoHide()
-                } else {
-                    print("Interruption Ended - playback should NOT resume")
-                }
-            }
-        }
+    public func disableAllControl() {
+
+        muteButton.isEnabled = false
+        playButton.isEnabled = false
+        forwardButton.isEnabled = false
+        backButton.isEnabled = false
+        playbackSlider.isEnabled = false
     }
 
     // MARK: -
 
     public func show(enableTimerAutoHide: Bool = false) {
 
-        guard let metadata = self.metadata, ncplayer != nil, !metadata.livePhoto else { return }
-        if metadata.classFile != NKCommon.TypeClassFile.video.rawValue && metadata.classFile != NKCommon.TypeClassFile.audio.rawValue { return }
-
-#if MFFFLIB
-        if MFFF.shared.existsMFFFSession(url: URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView))) {
-            self.hide()
-            return
-        }
-#endif
-
         timerAutoHide?.invalidate()
         if enableTimerAutoHide {
             startTimerAutoHide()
@@ -284,8 +178,6 @@ class NCPlayerToolBar: UIView {
         }, completion: { (_: Bool) in
             self.isHidden = false
         })
-
-        updateToolBar()
     }
 
     func isShow() -> Bool {
@@ -322,80 +214,49 @@ class NCPlayerToolBar: UIView {
         }
     }
 
-    func skip(seconds: Float64) {
-
-        guard let ncplayer = ncplayer, let player = ncplayer.player else { return }
-
-        let currentTime = player.currentTime()
-        var newTime: CMTime = .zero
-        let timeToAdd: CMTime = CMTimeMakeWithSeconds(abs(seconds), preferredTimescale: 1)
-
-        if seconds > 0 {
-            newTime = CMTimeAdd(currentTime, timeToAdd)
-            if newTime < ncplayer.durationTime {
-                ncplayer.videoSeek(time: newTime)
-            } else if newTime >= ncplayer.durationTime {
-                let timeToSubtract: CMTime = CMTimeMakeWithSeconds(3, preferredTimescale: 1)
-                newTime = CMTimeSubtract(ncplayer.durationTime, timeToSubtract)
-                if newTime > currentTime {
-                    ncplayer.videoSeek(time: newTime)
-                }
-            }
-        } else {
-            newTime = CMTimeSubtract(currentTime, timeToAdd)
-            if newTime.seconds < 0 {
-                newTime = .zero
-            }
-            ncplayer.videoSeek(time: newTime)
-        }
+    func stopTimerAutoHide() {
 
-        updateToolBar()
-        reStartTimerAutoHide()
+        timerAutoHide?.invalidate()
     }
 
-    func isPictureInPictureActive() -> Bool {
+    func playButtonPause() {
 
-        if let pictureInPictureController = self.pictureInPictureController, pictureInPictureController.isPictureInPictureActive {
-            return true
-        } else {
-            return false
-        }
+        playButton.setImage(NCUtility.shared.loadImage(named: "pause.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: 30)), for: .normal)
+        MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 1
     }
 
-    func stopTimerAutoHide() {
+    func playButtonPlay() {
 
-        timerAutoHide?.invalidate()
+        playButton.setImage(NCUtility.shared.loadImage(named: "play.fill", color: .white, symbolConfiguration: UIImage.SymbolConfiguration(pointSize: 30)), for: .normal)
+        MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] = 0
     }
 
     // MARK: - Event / Gesture
 
     @objc func onSliderValChanged(slider: UISlider, event: UIEvent) {
 
-        if let touchEvent = event.allTouches?.first, let ncplayer = ncplayer {
-
-            let seconds: Int64 = Int64(self.playbackSlider.value)
-            let targetTime: CMTime = CMTimeMake(value: seconds, timescale: 1)
-
-            switch touchEvent.phase {
-            case .began:
-                wasInPlay = ncplayer.isPlay()
-                ncplayer.playerPause()
-                playbackSliderEvent = .began
-            case .moved:
-                ncplayer.videoSeek(time: targetTime)
-                playbackSliderEvent = .moved
-            case .ended:
-                ncplayer.videoSeek(time: targetTime)
-                if wasInPlay {
-                    ncplayer.playerPlay()
-                }
-                playbackSliderEvent = .ended
-            default:
-                break
+        guard let touchEvent = event.allTouches?.first,
+              let ncplayer = ncplayer
+        else { return }
+
+        let newPosition = playbackSlider.value
+
+        switch touchEvent.phase {
+        case .began:
+            playbackSliderEvent = .began
+        case .moved:
+            ncplayer.playerPosition(newPosition)
+            playbackSliderEvent = .moved
+        case .ended:
+            ncplayer.playerPosition(newPosition)
+            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+                self.playbackSliderEvent = .ended
             }
-
-            reStartTimerAutoHide()
+        default:
+            break
         }
+
+        reStartTimerAutoHide()
     }
 
     // MARK: - Action
@@ -405,118 +266,47 @@ class NCPlayerToolBar: UIView {
     @objc func tapToolBarWith(gestureRecognizer: UITapGestureRecognizer) { }
 
     @IBAction func tapPlayerPause(_ sender: Any) {
+        guard let ncplayer = ncplayer else { return }
 
-        if ncplayer?.player?.timeControlStatus == .playing {
-            CCUtility.setPlayerPlay(false)
-            ncplayer?.playerPause()
-            ncplayer?.saveCurrentTime()
+        if ncplayer.isPlay() {
+            ncplayer.playerPause()
             timerAutoHide?.invalidate()
-        } else if ncplayer?.player?.timeControlStatus == .paused {
-            CCUtility.setPlayerPlay(true)
-            ncplayer?.playerPlay()
+        } else {
+            ncplayer.playerPlay()
             startTimerAutoHide()
-        } else if ncplayer?.player?.timeControlStatus == .waitingToPlayAtSpecifiedRate {
-            print("timeControlStatus.waitingToPlayAtSpecifiedRate")
-            if let reason = ncplayer?.player?.reasonForWaitingToPlay {
-                switch reason {
-                case .evaluatingBufferingRate:
-                    self.ncplayer?.player?.playImmediately(atRate: 1)
-                    print("reasonForWaitingToPlay.evaluatingBufferingRate")
-                case .toMinimizeStalls:
-                    print("reasonForWaitingToPlay.toMinimizeStalls")
-                case .noItemToPlay:
-                    print("reasonForWaitingToPlay.noItemToPlay")
-                default:
-                    print("Unknown \(reason)")
-                }
-            }
         }
     }
 
     @IBAction func tapMute(_ sender: Any) {
 
-        let mute = CCUtility.getAudioMute()
-
-        CCUtility.setAudioMute(!mute)
-        ncplayer?.player?.isMuted = !mute
-        updateToolBar()
-        reStartTimerAutoHide()
-    }
-
-    @IBAction func tapPip(_ sender: Any) {
+        guard let ncplayer = ncplayer else { return }
 
-        guard let videoLayer = ncplayer?.videoLayer else { return }
-
-        if let pictureInPictureController = self.pictureInPictureController, pictureInPictureController.isPictureInPictureActive {
-            pictureInPictureController.stopPictureInPicture()
-        }
-
-        if pictureInPictureController == nil {
-            pictureInPictureController = AVPictureInPictureController(playerLayer: videoLayer)
-            pictureInPictureController?.delegate = self
+        if CCUtility.getAudioVolume() > 0 {
+            CCUtility.setAudioVolume(0)
+            ncplayer.setVolumeAudio(0)
+            muteButton.setImage(NCUtility.shared.loadImage(named: "audioOff", color: .white), for: .normal)
+        } else {
+            CCUtility.setAudioVolume(100)
+            ncplayer.setVolumeAudio(100)
+            muteButton.setImage(NCUtility.shared.loadImage(named: "audioOn", color: .white), for: .normal)
         }
 
-        if let pictureInPictureController = pictureInPictureController, pictureInPictureController.isPictureInPicturePossible, let metadata = self.metadata {
-            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
-                pictureInPictureController.startPictureInPicture()
-                NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterHidePlayerToolBar, userInfo: ["ocId": metadata.ocId])
-            }
-        }
+        reStartTimerAutoHide()
     }
 
     @IBAction func tapForward(_ sender: Any) {
 
-        skip(seconds: 10)
-    }
-
-    @IBAction func tapBack(_ sender: Any) {
-
-        skip(seconds: -10)
-    }
-
-    @IBAction func tapSubtitle(_ sender: Any) {
-        self.ncplayer?.showAlertSubtitles()
-    }
-
-    func forward() {
-
-        var index: Int = 0
-
-        if let currentIndex = self.viewerMediaPage?.currentIndex, let metadatas = self.viewerMediaPage?.metadatas, let ncplayer = self.ncplayer {
-
-            if currentIndex == metadatas.count - 1 {
-                index = 0
-            } else {
-                index = currentIndex + 1
-            }
-
-            self.viewerMediaPage?.goTo(index: index, direction: .forward, autoPlay: ncplayer.isPlay())
-        }
-    }
-
-    func backward() {
-
-        var index: Int = 0
-
-        if let currentIndex = self.viewerMediaPage?.currentIndex, let metadatas = self.viewerMediaPage?.metadatas, let ncplayer = self.ncplayer {
-
-            if currentIndex == 0 {
-                index = metadatas.count - 1
-            } else {
-                index = currentIndex - 1
-            }
+        guard let ncplayer = ncplayer else { return }
 
-            self.viewerMediaPage?.goTo(index: index, direction: .reverse, autoPlay: ncplayer.isPlay())
-        }
+        ncplayer.jumpForward(10)
+        reStartTimerAutoHide()
     }
-}
 
-extension NCPlayerToolBar: AVPictureInPictureControllerDelegate {
+    @IBAction func tapBack(_ sender: Any) {
 
-    func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
+        guard let ncplayer = ncplayer else { return }
 
-        if let metadata = self.metadata, let ncplayer = self.ncplayer, !ncplayer.isPlay() {
-            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterShowPlayerToolBar, userInfo: ["ocId": metadata.ocId, "enableTimerAutoHide": false])
-        }
+        ncplayer.jumpBackward(10)
+        reStartTimerAutoHide()
     }
 }

+ 15 - 42
iOSClient/Viewer/NCViewerMedia/NCPlayer/NCPlayerToolBar.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
     <device id="retina6_1" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
         <capability name="Image references" minToolsVersion="12.0"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -16,33 +16,10 @@
             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
             <subviews>
                 <stackView opaque="NO" contentMode="scaleToFill" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="XfW-XC-eMf" userLabel="Player Top Tool Bar">
-                    <rect key="frame" x="299" y="54" width="105" height="35"/>
+                    <rect key="frame" x="369" y="58" width="35" height="35"/>
                     <subviews>
-                        <button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="a3d-ja-utA">
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Fml-c2-FMY">
                             <rect key="frame" x="5" y="5" width="25" height="25"/>
-                            <constraints>
-                                <constraint firstAttribute="height" constant="25" id="TIW-zu-jSX"/>
-                                <constraint firstAttribute="width" constant="25" id="jR3-bv-VnZ"/>
-                            </constraints>
-                            <state key="normal" image="pip.enter" catalog="system"/>
-                            <connections>
-                                <action selector="tapPip:" destination="iN0-l3-epB" eventType="touchUpInside" id="fB5-g1-OnB"/>
-                            </connections>
-                        </button>
-                        <button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Mqe-Hd-USv">
-                            <rect key="frame" x="40" y="5" width="25" height="25"/>
-                            <constraints>
-                                <constraint firstAttribute="width" constant="25" id="HQs-6R-hb9"/>
-                                <constraint firstAttribute="height" constant="25" id="s43-Qc-Shn"/>
-                            </constraints>
-                            <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
-                            <state key="normal" image="captions.bubble" catalog="system"/>
-                            <connections>
-                                <action selector="tapSubtitle:" destination="iN0-l3-epB" eventType="touchUpInside" id="Cxi-6E-bKD"/>
-                            </connections>
-                        </button>
-                        <button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Fml-c2-FMY">
-                            <rect key="frame" x="75" y="5" width="25" height="25"/>
                             <constraints>
                                 <constraint firstAttribute="width" constant="25" id="S8g-UR-4zh"/>
                                 <constraint firstAttribute="height" constant="25" id="zjo-O1-SI2"/>
@@ -62,7 +39,7 @@
                         <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ixi-yR-HDH" userLabel="Container play">
                             <rect key="frame" x="0.0" y="0.0" width="118" height="58"/>
                             <subviews>
-                                <button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uP7-aY-x4n">
+                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uP7-aY-x4n">
                                     <rect key="frame" x="3.5" y="9" width="40" height="40"/>
                                     <constraints>
                                         <constraint firstAttribute="width" constant="40" id="0jv-Tl-Nch"/>
@@ -73,7 +50,7 @@
                                         <action selector="tapBack:" destination="iN0-l3-epB" eventType="touchUpInside" id="q3g-BH-iUc"/>
                                     </connections>
                                 </button>
-                                <button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hx9-d5-yiD">
+                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hx9-d5-yiD">
                                     <rect key="frame" x="39" y="9" width="40" height="40"/>
                                     <constraints>
                                         <constraint firstAttribute="width" constant="40" id="Ime-ag-2Hm"/>
@@ -87,7 +64,7 @@
                                         <action selector="tapPlayerPause:" destination="iN0-l3-epB" eventType="touchUpInside" id="XHf-om-3g9"/>
                                     </connections>
                                 </button>
-                                <button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bGn-IC-3V1">
+                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bGn-IC-3V1">
                                     <rect key="frame" x="74.5" y="9" width="40" height="40"/>
                                     <constraints>
                                         <constraint firstAttribute="height" constant="40" id="VDT-no-B6f"/>
@@ -109,17 +86,17 @@
                                 <constraint firstItem="bGn-IC-3V1" firstAttribute="centerX" secondItem="ixi-yR-HDH" secondAttribute="centerX" multiplier="1.6" id="wJP-ph-5c5"/>
                             </constraints>
                         </view>
-                        <slider opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="MY0-FC-j88">
+                        <slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="MY0-FC-j88">
                             <rect key="frame" x="121" y="14" width="265" height="31"/>
                         </slider>
-                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="99:99:99" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="svM-TQ-AyQ">
-                            <rect key="frame" x="338.5" y="44" width="45.5" height="12"/>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="--:--" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="svM-TQ-AyQ">
+                            <rect key="frame" x="361.5" y="44" width="22.5" height="12"/>
                             <fontDescription key="fontDescription" type="system" pointSize="10"/>
                             <nil key="textColor"/>
                             <nil key="highlightedColor"/>
                         </label>
-                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00:00" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OHB-2J-Gqb">
-                            <rect key="frame" x="123" y="44" width="45" height="12"/>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="--:--" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OHB-2J-Gqb">
+                            <rect key="frame" x="123" y="44" width="22.5" height="12"/>
                             <fontDescription key="fontDescription" type="system" pointSize="10"/>
                             <nil key="textColor"/>
                             <nil key="highlightedColor"/>
@@ -157,22 +134,18 @@
                 <outlet property="labelCurrentTime" destination="OHB-2J-Gqb" id="pFy-CJ-x2A"/>
                 <outlet property="labelLeftTime" destination="svM-TQ-AyQ" id="UDV-Lh-12z"/>
                 <outlet property="muteButton" destination="Fml-c2-FMY" id="Fo1-Ep-ZPz"/>
-                <outlet property="pipButton" destination="a3d-ja-utA" id="9nt-RD-IXd"/>
                 <outlet property="playButton" destination="hx9-d5-yiD" id="Enk-Ge-2Yx"/>
                 <outlet property="playbackSlider" destination="MY0-FC-j88" id="bVe-Kc-80k"/>
                 <outlet property="playerToolBarView" destination="85m-50-8yp" id="eZK-p1-v65"/>
                 <outlet property="playerTopToolBarView" destination="XfW-XC-eMf" id="Qdp-IW-YhT"/>
-                <outlet property="subtitleButton" destination="Mqe-Hd-USv" id="ZHN-bf-WT5"/>
             </connections>
             <point key="canvasLocation" x="137.68115942028987" y="152.67857142857142"/>
         </view>
     </objects>
     <resources>
         <image name="audioOn" width="28" height="28"/>
-        <image name="captions.bubble" catalog="system" width="128" height="110"/>
-        <image name="gobackward.10" catalog="system" width="121" height="128"/>
-        <image name="goforward.10" catalog="system" width="121" height="128"/>
-        <image name="pip.enter" catalog="system" width="128" height="96"/>
-        <image name="play.fill" catalog="system" width="116" height="128"/>
+        <image name="gobackward.10" catalog="system" width="119" height="128"/>
+        <image name="goforward.10" catalog="system" width="119" height="128"/>
+        <image name="play.fill" catalog="system" width="117" height="128"/>
     </resources>
 </document>

+ 0 - 407
iOSClient/Viewer/NCViewerMedia/NCPlayer/NCSubtitle/NCSubtitlePlayer.swift

@@ -1,407 +0,0 @@
-//
-//  NCSubtitlePlayer.swift
-//  Nextcloud
-//
-//  Created by Federico Malagoni on 18/02/22.
-//  Copyright © 2022 Federico Malagoni. All rights reserved.
-//  Copyright © 2022 Marino Faggiana All rights reserved.
-//
-//  Author Federico Malagoni <federico.malagoni@astrairidium.com>
-//  Author Marino Faggiana <marino.faggiana@nextcloud.com>
-//
-//  This program is free software: you can redistribute it and/or modify
-//  it under the terms of the GNU General Public License as published by
-//  the Free Software Foundation, either version 3 of the License, or
-//  (at your option) any later version.
-//
-//  This program is distributed in the hope that it will be useful,
-//  but WITHOUT ANY WARRANTY; without even the implied warranty of
-//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//  GNU General Public License for more details.
-//
-//  You should have received a copy of the GNU General Public License
-//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
-//
-
-import Foundation
-import AVKit
-import NextcloudKit
-
-extension NCPlayer {
-
-    private struct AssociatedKeys {
-        static var FontKey = "FontKey"
-        static var ColorKey = "FontKey"
-        static var SubtitleKey = "SubtitleKey"
-        static var SubtitleContainerViewKey = "SubtitleContainerViewKey"
-        static var SubtitleContainerViewHeightKey = "SubtitleContainerViewHeightKey"
-        static var SubtitleHeightKey = "SubtitleHeightKey"
-        static var SubtitleWidthKey = "SubtitleWidthKey"
-        static var SubtitleContainerViewWidthKey = "SubtitleContainerViewWidthKey"
-        static var SubtitleBottomKey = "SubtitleBottomKey"
-        static var PayloadKey = "PayloadKey"
-    }
-
-    private var widthProportion: CGFloat {
-        return 0.9
-    }
-
-    private var bottomConstantPortrait: CGFloat {
-        get {
-            if UIDevice.current.hasNotch {
-                return -60
-            } else {
-                return -40
-            }
-        } set {
-            _ = newValue
-        }
-    }
-
-    private var bottomConstantLandscape: CGFloat {
-        get {
-            if UIDevice.current.hasNotch {
-                return -120
-            } else {
-                return -100
-            }
-        } set {
-            _ = newValue
-        }
-    }
-
-    var subtitleContainerView: UIView? {
-        get { return objc_getAssociatedObject(self, &AssociatedKeys.SubtitleContainerViewKey) as? UIView }
-        set (value) { objc_setAssociatedObject(self, &AssociatedKeys.SubtitleContainerViewKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)}
-    }
-
-    var subtitleLabel: UILabel? {
-        get { return objc_getAssociatedObject(self, &AssociatedKeys.SubtitleKey) as? UILabel }
-        set (value) { objc_setAssociatedObject(self, &AssociatedKeys.SubtitleKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
-    }
-
-    fileprivate var subtitleLabelHeightConstraint: NSLayoutConstraint? {
-        get { return objc_getAssociatedObject(self, &AssociatedKeys.SubtitleHeightKey) as? NSLayoutConstraint }
-        set (value) { objc_setAssociatedObject(self, &AssociatedKeys.SubtitleHeightKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
-    }
-
-    fileprivate var subtitleContainerViewHeightConstraint: NSLayoutConstraint? {
-        get { return objc_getAssociatedObject(self, &AssociatedKeys.SubtitleContainerViewHeightKey) as? NSLayoutConstraint }
-        set (value) { objc_setAssociatedObject(self, &AssociatedKeys.SubtitleContainerViewHeightKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
-    }
-
-    fileprivate var subtitleLabelBottomConstraint: NSLayoutConstraint? {
-        get { return objc_getAssociatedObject(self, &AssociatedKeys.SubtitleBottomKey) as? NSLayoutConstraint }
-        set (value) { objc_setAssociatedObject(self, &AssociatedKeys.SubtitleBottomKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
-    }
-
-    fileprivate var subtitleLabelWidthConstraint: NSLayoutConstraint? {
-        get { return objc_getAssociatedObject(self, &AssociatedKeys.SubtitleWidthKey) as? NSLayoutConstraint }
-        set (value) { objc_setAssociatedObject(self, &AssociatedKeys.SubtitleWidthKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
-    }
-    fileprivate var subtitleContainerViewWidthConstraint: NSLayoutConstraint? {
-        get { return objc_getAssociatedObject(self, &AssociatedKeys.SubtitleContainerViewWidthKey) as? NSLayoutConstraint }
-        set (value) { objc_setAssociatedObject(self, &AssociatedKeys.SubtitleContainerViewWidthKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
-    }
-
-    fileprivate var parsedPayload: NSDictionary? {
-        get { return objc_getAssociatedObject(self, &AssociatedKeys.PayloadKey) as? NSDictionary }
-        set (value) { objc_setAssociatedObject(self, &AssociatedKeys.PayloadKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
-    }
-
-    func setUpForSubtitle() {
-        self.subtitleUrls.removeAll()
-        if let url = CCUtility.getDirectoryProviderStorageOcId(metadata.ocId) {
-            let enumerator = FileManager.default.enumerator(atPath: url)
-            let filePaths = (enumerator?.allObjects as? [String])
-            if let filePaths = filePaths {
-                let txtFilePaths = (filePaths.filter { $0.contains(".srt") }).sorted {
-                    guard let str1LastChar = $0.dropLast(4).last, let str2LastChar = $1.dropLast(4).last else {
-                        return false
-                    }
-                    return str1LastChar < str2LastChar
-                }
-                for txtFilePath in txtFilePaths {
-                    let subtitleUrl = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: txtFilePath))
-                    self.subtitleUrls.append(subtitleUrl)
-                }
-            }
-        }
-        let (all, existing) = NCManageDatabase.shared.getSubtitles(account: metadata.account, serverUrl: metadata.serverUrl, fileName: metadata.fileName)
-        if !existing.isEmpty {
-            for subtitle in existing {
-                let subtitleUrl = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(subtitle.ocId, fileNameView: subtitle.fileName))
-                self.subtitleUrls.append(subtitleUrl)
-            }
-        }
-        if all.count != existing.count {
-            let error = NKError(errorCode: NCGlobal.shared.errorInternalError, errorDescription: "_subtitle_not_dowloaded_")
-            NCContentPresenter.shared.showInfo(error: error)
-        }
-        self.setSubtitleToolbarIcon(subtitleUrls: subtitleUrls)
-        self.hideSubtitle()
-    }
-
-    func setSubtitleToolbarIcon(subtitleUrls: [URL]) {
-        if subtitleUrls.isEmpty {
-            playerToolBar?.subtitleButton.isHidden = true
-        } else {
-            playerToolBar?.subtitleButton.isHidden = false
-        }
-    }
-
-    func addSubtitlesTo(_ vc: UIViewController, _ playerToolBar: NCPlayerToolBar?) {
-        addSubtitleLabel(vc, playerToolBar)
-        NotificationCenter.default.addObserver(self, selector: #selector(deviceRotated(_:)), name: UIDevice.orientationDidChangeNotification, object: nil)
-    }
-
-    func loadText(filePath: URL, _ completion: @escaping (_ contents: String?) -> Void) {
-        DispatchQueue.global().async {
-            guard let data = try? Data(contentsOf: filePath),
-                  let encoding = NCUtility.shared.getEncondingDataType(data: data) else {
-                return
-            }
-            if let decodedString = String(data: data, encoding: encoding) {
-                completion(decodedString)
-            } else {
-                completion(nil)
-            }
-         }
-    }
-
-    func open(fileFromLocal url: URL) {
-
-        subtitleLabel?.text = ""
-
-        self.loadText(filePath: url) { contents in
-            guard let contents = contents else {
-                return
-            }
-            DispatchQueue.main.async {
-                self.subtitleLabel?.text = ""
-                self.show(subtitles: contents)
-            }
-        }
-    }
-
-    @objc public func hideSubtitle() {
-        self.subtitleLabel?.isHidden = true
-        self.subtitleContainerView?.isHidden = true
-        self.currentSubtitle = nil
-    }
-
-    @objc public func showSubtitle(url: URL) {
-        self.subtitleLabel?.isHidden = false
-        self.subtitleContainerView?.isHidden = false
-        self.currentSubtitle = url
-    }
-
-    private func show(subtitles string: String) {
-        parsedPayload = try? NCSubtitles.parseSubRip(string)
-        if let parsedPayload = parsedPayload {
-            addPeriodicNotification(parsedPayload: parsedPayload)
-        }
-    }
-
-    private func showByDictionary(dictionaryContent: NSMutableDictionary) {
-        parsedPayload = dictionaryContent
-        if let parsedPayload = parsedPayload {
-            addPeriodicNotification(parsedPayload: parsedPayload)
-        }
-    }
-
-    func addPeriodicNotification(parsedPayload: NSDictionary) {
-        // Add periodic notifications
-        let interval = CMTimeMake(value: 1, timescale: 60)
-        self.player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
-            guard let strongSelf = self, let label = strongSelf.subtitleLabel, let containerView = strongSelf.subtitleContainerView else {
-                return
-            }
-            DispatchQueue.main.async {
-                label.text = NCSubtitles.searchSubtitles(strongSelf.parsedPayload, time.seconds)
-                strongSelf.adjustViewWidth(containerView: containerView)
-                strongSelf.adjustLabelHeight(label: label)
-            }
-        }
-    }
-
-    @objc private func deviceRotated(_ notification: Notification) {
-        guard let label = self.subtitleLabel,
-              let containerView = self.subtitleContainerView else { return }
-        DispatchQueue.main.async {
-            self.adjustViewWidth(containerView: containerView)
-            self.adjustLabelHeight(label: label)
-            self.adjustLabelBottom(label: label)
-            containerView.layoutIfNeeded()
-            label.layoutIfNeeded()
-        }
-    }
-
-    private func adjustLabelHeight(label: UILabel) {
-        let baseSize = CGSize(width: label.bounds.width, height: .greatestFiniteMagnitude)
-        let rect = label.sizeThatFits(baseSize)
-        if label.text != nil {
-            self.subtitleLabelHeightConstraint?.constant = rect.height + 5.0
-        } else {
-            self.subtitleLabelHeightConstraint?.constant = rect.height
-        }
-    }
-
-    private func adjustLabelBottom(label: UILabel) {
-        var bottomConstant: CGFloat = bottomConstantPortrait
-
-        switch UIApplication.shared.windows.first?.windowScene?.interfaceOrientation {
-        case .portrait:
-            bottomConstant = bottomConstantLandscape
-        case .landscapeLeft, .landscapeRight, .portraitUpsideDown:
-            bottomConstant = bottomConstantPortrait
-        default:
-            ()
-        }
-        subtitleLabelBottomConstraint?.constant = bottomConstant
-    }
-
-    private func adjustViewWidth(containerView: UIView) {
-        let widthConstant: CGFloat = UIScreen.main.bounds.width * widthProportion
-        subtitleContainerViewWidthConstraint!.constant = widthConstant
-        subtitleLabel?.preferredMaxLayoutWidth = (widthConstant - 20)
-    }
-
-    fileprivate func addSubtitleLabel(_ vc: UIViewController, _ playerToolBar: NCPlayerToolBar?) {
-        guard subtitleLabel == nil,
-              subtitleContainerView == nil else {
-                  return
-              }
-        subtitleContainerView = UIView()
-        subtitleLabel = UILabel()
-
-        subtitleContainerView?.translatesAutoresizingMaskIntoConstraints = false
-        subtitleContainerView?.layer.cornerRadius = 5.0
-        subtitleContainerView?.layer.masksToBounds = true
-        subtitleContainerView?.layer.shouldRasterize = true
-        subtitleContainerView?.layer.rasterizationScale = UIScreen.main.scale
-        subtitleContainerView?.backgroundColor = UIColor.black.withAlphaComponent(0.35)
-
-        subtitleLabel?.translatesAutoresizingMaskIntoConstraints = false
-        subtitleLabel?.textAlignment = .center
-        subtitleLabel?.numberOfLines = 0
-        let fontSize = UIDevice.current.userInterfaceIdiom == .pad ? 38.0 : 20.0
-        subtitleLabel?.font = UIFont.incosolataMedium(size: fontSize)
-        subtitleLabel?.lineBreakMode = .byWordWrapping
-        subtitleLabel?.textColor = .white
-        subtitleLabel?.backgroundColor = .clear
-
-        subtitleContainerView?.addSubview(subtitleLabel!)
-
-        var isFound = false
-
-        for v in vc.view.subviews where v is UIScrollView {
-            if let scrollView = v as? UIScrollView {
-                for subView in scrollView.subviews where subView is imageVideoContainerView {
-                    subView.addSubview(subtitleContainerView!)
-                    isFound = true
-                    break
-                }
-            }
-        }
-
-        if !isFound {
-            vc.view.addSubview(subtitleContainerView!)
-        }
-
-        NSLayoutConstraint.activate([
-            subtitleLabel!.centerXAnchor.constraint(equalTo: subtitleContainerView!.centerXAnchor),
-            subtitleLabel!.centerYAnchor.constraint(equalTo: subtitleContainerView!.centerYAnchor)
-        ])
-
-        subtitleContainerViewHeightConstraint = NSLayoutConstraint(item: subtitleContainerView!, attribute: .height, relatedBy: .equal, toItem: subtitleLabel!, attribute: .height, multiplier: 1.0, constant: 0.0)
-        vc.view?.addConstraint(subtitleContainerViewHeightConstraint!)
-
-        var bottomConstant: CGFloat = bottomConstantPortrait
-
-        switch UIApplication.shared.windows.first?.windowScene?.interfaceOrientation {
-        case .portrait, .portraitUpsideDown:
-            bottomConstant = bottomConstantLandscape
-        case .landscapeLeft, .landscapeRight:
-            bottomConstant = bottomConstantPortrait
-        default:
-            ()
-        }
-
-        let widthConstant: CGFloat = UIScreen.main.bounds.width * widthProportion
-
-        NSLayoutConstraint.activate([
-            subtitleContainerView!.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor)
-        ])
-
-        subtitleContainerViewWidthConstraint = NSLayoutConstraint(item: subtitleContainerView!, attribute: .width, relatedBy: .lessThanOrEqual, toItem: nil,
-                                                                  attribute: .width, multiplier: 1, constant: widthConstant)
-
-        // setting default width == 0 because there is no text inside of the label
-        subtitleLabelWidthConstraint = NSLayoutConstraint(item: subtitleLabel!, attribute: .width, relatedBy: .equal, toItem: subtitleContainerView,
-                                                          attribute: .width, multiplier: 1, constant: -20)
-
-        subtitleLabelBottomConstraint = NSLayoutConstraint(item: subtitleContainerView!, attribute: .bottom, relatedBy: .equal, toItem: vc.view, attribute:
-                                                                .bottom, multiplier: 1, constant: bottomConstant)
-
-        vc.view?.addConstraint(subtitleContainerViewWidthConstraint!)
-        vc.view?.addConstraint(subtitleLabelWidthConstraint!)
-        vc.view?.addConstraint(subtitleLabelBottomConstraint!)
-    }
-
-    internal func showAlertSubtitles() {
-
-        let alert = UIAlertController(title: nil, message: NSLocalizedString("_subtitle_", comment: ""), preferredStyle: .actionSheet)
-
-        for url in subtitleUrls {
-
-            print("Play Subtitle at:\n\(url.path)")
-
-            let videoUrlTitle = self.metadata.fileName.alphanumeric.dropLast(3)
-            let subtitleUrlTitle = url.lastPathComponent.alphanumeric.dropLast(3)
-
-            var titleSubtitle = String(subtitleUrlTitle.dropFirst(videoUrlTitle.count))
-            if titleSubtitle.isEmpty {
-                titleSubtitle = NSLocalizedString("_subtitle_", comment: "")
-            }
-
-            let action = UIAlertAction(title: titleSubtitle, style: .default, handler: { [self] _ in
-
-                if NCUtilityFileSystem.shared.getFileSize(filePath: url.path) > 0 {
-
-                    self.open(fileFromLocal: url)
-                    if let viewController = viewController {
-                        self.addSubtitlesTo(viewController, self.playerToolBar)
-                        self.showSubtitle(url: url)
-                    }
-
-                } else {
-
-                    let alertError = UIAlertController(title: NSLocalizedString("_error_", comment: ""), message: NSLocalizedString("_subtitle_not_found_", comment: ""), preferredStyle: .alert)
-                    alertError.addAction(UIKit.UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: nil))
-
-                    viewController?.present(alertError, animated: true, completion: nil)
-                }
-            })
-            alert.addAction(action)
-            if currentSubtitle == url {
-                action.setValue(true, forKey: "checked")
-            }
-        }
-
-        let disable = UIAlertAction(title: NSLocalizedString("_disable_", comment: ""), style: .default, handler: { _ in
-            self.hideSubtitle()
-        })
-        alert.addAction(disable)
-        if currentSubtitle == nil {
-            disable.setValue(true, forKey: "checked")
-        }
-
-        alert.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel, handler: { _ in
-        }))
-
-        alert.popoverPresentationController?.sourceView = self.viewController?.view
-
-        self.viewController?.present(alert, animated: true, completion: nil)
-    }
-}

+ 0 - 150
iOSClient/Viewer/NCViewerMedia/NCPlayer/NCSubtitle/NCSubtitles.swift

@@ -1,150 +0,0 @@
-//
-//  NCSubtitles
-//  Nextcloud
-//
-//  Created by Marc Hervera.
-//  Copyright 2017 Marc Hervera AVPlayerViewController-Subtitles v1.3.1 iOS
-//
-//  Modified by Federico Malagoni on 23/02/22 for Nextcloud.
-//
-//  Licensed under Apache License v2.0.
-//
-
-import AVKit
-
-class NCSubtitles {
-
-    // MARK: - Private properties
-
-    private var parsedPayload: NSDictionary?
-
-    // MARK: - Public methods
-
-    public init(file filePath: URL, encoding: String.Encoding = .utf8) throws {
-        // Get string
-        let string = try String(contentsOf: filePath, encoding: encoding)
-        // Parse string
-        parsedPayload = try NCSubtitles.parseSubRip(string)
-    }
-
-    public init(subtitles string: String) throws {
-        // Parse string
-        parsedPayload = try NCSubtitles.parseSubRip(string)
-    }
-
-    /// Search subtitles at time
-    ///
-    /// - Parameter time: Time
-    /// - Returns: String if exists
-    public func searchSubtitles(at time: TimeInterval) -> String? {
-        return NCSubtitles.searchSubtitles(parsedPayload, time)
-    }
-
-    // MARK: - Static methods
-
-    /// Subtitle parser
-    ///
-    /// - Parameter payload: Input string
-    /// - Returns: NSDictionary
-    static func parseSubRip(_ payload: String) throws -> NSDictionary? {
-        // Prepare payload
-        var payload = payload.replacingOccurrences(of: "\n\r\n", with: "\n\n")
-        payload = payload.replacingOccurrences(of: "\n\n\n", with: "\n\n")
-        payload = payload.replacingOccurrences(of: "\r\n", with: "\n")
-
-        // Parsed dict
-        let parsed = NSMutableDictionary()
-
-        // Get groups
-        let regexStr = "(\\d+)\\n([\\d:,.]+)\\s+-{2}\\>\\s+([\\d:,.]+)\\n([\\s\\S]*?(?=\\n{2,}|$))"
-        let regex = try NSRegularExpression(pattern: regexStr, options: .caseInsensitive)
-        let matches = regex.matches(in: payload, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: payload.count))
-
-        for m in matches {
-            let group = (payload as NSString).substring(with: m.range)
-
-            // Get index
-            var regex = try NSRegularExpression(pattern: "^[0-9]+", options: .caseInsensitive)
-            var match = regex.matches(in: group, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: group.count))
-
-            guard let i = match.first else {
-                continue
-            }
-
-            let index = (group as NSString).substring(with: i.range)
-
-            // Get "from" & "to" time
-            regex = try NSRegularExpression(pattern: "\\d{1,2}:\\d{1,2}:\\d{1,2}[,.]\\d{1,3}", options: .caseInsensitive)
-            match = regex.matches(in: group, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: group.count))
-
-            guard match.count == 2 else {
-                continue
-            }
-
-            guard let from = match.first, let to = match.last else {
-                continue
-            }
-
-            var h: TimeInterval = 0.0, m: TimeInterval = 0.0, s: TimeInterval = 0.0, c: TimeInterval = 0.0
-
-            let fromStr = (group as NSString).substring(with: from.range)
-            var scanner = Scanner(string: fromStr)
-            scanner.scanDouble(&h)
-            scanner.scanString(":", into: nil)
-            scanner.scanDouble(&m)
-            scanner.scanString(":", into: nil)
-            scanner.scanDouble(&s)
-            scanner.scanString(",", into: nil)
-            scanner.scanDouble(&c)
-            let fromTime = (h * 3600.0) + (m * 60.0) + s + (c / 1000.0)
-
-            let toStr = (group as NSString).substring(with: to.range)
-            scanner = Scanner(string: toStr)
-            scanner.scanDouble(&h)
-            scanner.scanString(":", into: nil)
-            scanner.scanDouble(&m)
-            scanner.scanString(":", into: nil)
-            scanner.scanDouble(&s)
-            scanner.scanString(",", into: nil)
-            scanner.scanDouble(&c)
-            let toTime = (h * 3600.0) + (m * 60.0) + s + (c / 1000.0)
-
-            // Get text & check if empty
-            let range = NSRange(location: 0, length: to.range.location + to.range.length + 1)
-            guard (group as NSString).length - range.length > 0 else {
-                continue
-            }
-
-            let text = (group as NSString).replacingCharacters(in: range, with: "")
-
-            // Create final object
-            let final = NSMutableDictionary()
-            final["from"] = fromTime
-            final["to"] = toTime
-            final["text"] = text
-            parsed[index] = final
-        }
-
-        return parsed
-    }
-
-    /// Search subtitle on time
-    ///
-    /// - Parameters:
-    ///   - payload: Inout payload
-    ///   - time: Time
-    /// - Returns: String
-    static func searchSubtitles(_ payload: NSDictionary?, _ time: TimeInterval) -> String? {
-        let predicate = NSPredicate(format: "(%f >= %K) AND (%f <= %K)", time, "from", time, "to")
-
-        guard let values = payload?.allValues, let result = (values as NSArray).filtered(using: predicate).first as? NSDictionary else {
-            return nil
-        }
-
-        guard let text = result.value(forKey: "text") as? String else {
-            return nil
-        }
-
-        return text.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
-    }
-}

+ 53 - 24
iOSClient/Viewer/NCViewerMedia/NCViewerMedia.swift

@@ -39,7 +39,6 @@ class NCViewerMedia: UIViewController {
     @IBOutlet weak var statusLabel: UILabel!
     @IBOutlet weak var detailView: NCViewerMediaDetailView!
 
-    private var _autoPlay: Bool = false
     private var tipView: EasyTipView?
 
     let appDelegate = UIApplication.shared.delegate as! AppDelegate
@@ -53,16 +52,8 @@ class NCViewerMedia: UIViewController {
     var imageViewConstraint: CGFloat = 0
     var isDetailViewInitializze: Bool = false
 
-    var autoPlay: Bool {
-        get {
-            let temp = _autoPlay
-            _autoPlay = false
-            return temp
-        }
-        set(newVal) {
-            _autoPlay = newVal
-        }
-    }
+    var avPlayerLayer: AVPlayerLayer?
+    var avPlayer: AVPlayer?
 
     // MARK: - View Life Cycle
 
@@ -107,13 +98,9 @@ class NCViewerMedia: UIViewController {
                 playerToolBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
                 playerToolBar.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
                 playerToolBar.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
-                playerToolBar.viewerMediaPage = viewerMediaPage
             }
 
-            let urlVideo = NCKTVHTTPCache.shared.getVideoURL(metadata: metadata)
-            if let url = urlVideo.url {
-                self.ncplayer = NCPlayer.init(url: url, autoPlay: self.autoPlay, isProxy: urlVideo.isProxy, imageVideoContainer: self.imageVideoContainer, playerToolBar: self.playerToolBar, metadata: self.metadata, detailView: self.detailView, viewController: self)
-            }
+            self.ncplayer = NCPlayer(imageVideoContainer: self.imageVideoContainer, playerToolBar: self.playerToolBar, metadata: self.metadata, viewerMediaPage: self.viewerMediaPage)
         }
 
         // TIP
@@ -183,8 +170,15 @@ class NCViewerMedia: UIViewController {
         if metadata.classFile == NKCommon.TypeClassFile.video.rawValue || metadata.classFile == NKCommon.TypeClassFile.audio.rawValue {
 
             if let ncplayer = self.ncplayer {
-                ncplayer.openAVPlayer()
-                self.viewerMediaPage?.updateCommandCenter(ncplayer: ncplayer, metadata: self.metadata)
+
+                if ncplayer.url == nil {
+                    NCNetworking.shared.getVideoUrl(metadata: metadata) { url in
+                        if let url = url {
+                            ncplayer.openAVPlayer(url: url)
+                            self.viewerMediaPage?.updateCommandCenter(ncplayer: ncplayer, metadata: self.metadata)
+                        }
+                    }
+                }
             }
             
         } else if metadata.classFile == NKCommon.TypeClassFile.image.rawValue {
@@ -216,6 +210,15 @@ class NCViewerMedia: UIViewController {
                     self.openDetail()
                 }
             }
+
+            /*
+            if let ncplayer = self.ncplayer {
+
+                ncplayer.imageVideoContainer?.frame = self.imageVideoContainer.frame
+                ncplayer.imageVideoContainer?.frame.size = self.imageVideoContainer.frame.size
+                //ncplayer.imageVideoContainer?.resizeContentView()
+            }
+            */
         }, completion: { context in
             self.showTip()
         })
@@ -333,13 +336,40 @@ class NCViewerMedia: UIViewController {
         }
     }
 
+    // MARK: - Live Photo
+
+    func playLivePhoto(filePath: String) {
+
+        updateViewConstraints()
+        statusViewImage.isHidden = true
+        statusLabel.isHidden = true
+
+        avPlayer = AVPlayer(url: URL(fileURLWithPath: filePath))
+        avPlayerLayer = AVPlayerLayer(player: avPlayer)
+
+        if let avPlayerLayer = self.avPlayerLayer, let imageView = imageVideoContainer {
+            avPlayerLayer.videoGravity = .resizeAspect
+            avPlayerLayer.frame = imageView.bounds
+            imageView.layer.addSublayer(avPlayerLayer)
+            imageView.playerLayer = avPlayerLayer
+            avPlayer?.play()
+        }
+    }
+
+    func stopLivePhoto() {
+
+        avPlayer?.pause()
+        avPlayerLayer?.removeFromSuperlayer()
+
+        statusViewImage.isHidden = false
+        statusLabel.isHidden = false
+    }
+
     // MARK: - Gesture
 
     @objc func didDoubleTapWith(gestureRecognizer: UITapGestureRecognizer) {
 
-        if detailView.isShow() { return }
-        // NO ZOOM for Audio
-        if metadata.classFile == NKCommon.TypeClassFile.audio.rawValue { return }
+        guard metadata.classFile == NKCommon.TypeClassFile.image.rawValue, !detailView.isShow()  else { return }
 
         let pointInView = gestureRecognizer.location(in: self.imageVideoContainer)
         var newZoomScale = self.scrollView.maximumZoomScale
@@ -358,6 +388,8 @@ class NCViewerMedia: UIViewController {
 
     @objc func didPanWith(gestureRecognizer: UIPanGestureRecognizer) {
 
+        guard metadata.classFile == NKCommon.TypeClassFile.image.rawValue else { return }
+
         let currentLocation = gestureRecognizer.translation(in: self.view)
 
         switch gestureRecognizer.state {
@@ -483,9 +515,6 @@ extension NCViewerMedia {
         }
 
         scrollView.pinchGestureRecognizer?.isEnabled = true
-        if metadata.classFile == NKCommon.TypeClassFile.video.rawValue && !metadata.livePhoto && ncplayer?.player?.timeControlStatus == .paused {
-            NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterShowPlayerToolBar, userInfo: ["ocId": metadata.ocId, "enableTimerAutoHide": false])
-        }
     }
 
     func reloadDetail() {

+ 4 - 11
iOSClient/Viewer/NCViewerMedia/NCViewerMediaDetailView.swift

@@ -140,17 +140,10 @@ class NCViewerMediaDetailView: UIView {
         }
         dateValue.textColor = textColor
 
-        // Dimension / Duration
-        if metadata.classFile == NKCommon.TypeClassFile.image.rawValue {
-            if let image = image {
-                dimLabel.text = NSLocalizedString("_resolution_", comment: "")
-                dimValue.text = "\(Int(image.size.width)) x \(Int(image.size.height))"
-            }
-        } else if metadata.classFile == NKCommon.TypeClassFile.video.rawValue || metadata.classFile == NKCommon.TypeClassFile.audio.rawValue {
-            if let durationTime = NCManageDatabase.shared.getVideoDurationTime(metadata: metadata) {
-                self.dimLabel.text = NSLocalizedString("_duration_", comment: "")
-                self.dimValue.text = NCUtility.shared.stringFromTime(durationTime)
-            }
+        // Dimension
+        if let image = image {
+            dimLabel.text = NSLocalizedString("_resolution_", comment: "")
+            dimValue.text = "\(Int(image.size.width)) x \(Int(image.size.height))"
         }
         dimValue.textColor = textColor
 

+ 36 - 54
iOSClient/Viewer/NCViewerMedia/NCViewerMediaPage.swift

@@ -24,6 +24,8 @@
 import UIKit
 import NextcloudKit
 import MediaPlayer
+import JGProgressHUD
+import Alamofire
 
 class NCViewerMediaPage: UIViewController {
 
@@ -53,7 +55,6 @@ class NCViewerMediaPage: UIViewController {
     var modifiedOcId: [String] = []
     var currentIndex = 0
     var nextIndex: Int?
-    var ncplayerLivePhoto: NCPlayer?
     var panGestureRecognizer: UIPanGestureRecognizer!
     var singleTapGestureRecognizer: UITapGestureRecognizer!
     var longtapGestureRecognizer: UILongPressGestureRecognizer!
@@ -108,8 +109,6 @@ class NCViewerMediaPage: UIViewController {
         NotificationCenter.default.addObserver(self, selector: #selector(hidePlayerToolBar(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterHidePlayerToolBar), object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(showPlayerToolBar(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterShowPlayerToolBar), object: nil)
 
-        NotificationCenter.default.addObserver(self, selector: #selector(reloadMediaPage(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadMediaPage), object: nil)
-
         NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil)
     }
 
@@ -128,8 +127,6 @@ class NCViewerMediaPage: UIViewController {
         NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterHidePlayerToolBar), object: nil)
         NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterShowPlayerToolBar), object: nil)
 
-        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterReloadMediaPage), object: nil)
-
         NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterApplicationDidBecomeActive), object: nil)
     }
 
@@ -138,7 +135,6 @@ class NCViewerMediaPage: UIViewController {
 
         if let ncplayer = currentViewController.ncplayer, ncplayer.isPlay() {
             ncplayer.playerPause()
-            ncplayer.saveCurrentTime()
         }
         currentViewController.playerToolBar?.stopTimerAutoHide()
         clearCommandCenter()
@@ -194,8 +190,9 @@ class NCViewerMediaPage: UIViewController {
             hideStatusBar = false
             progressView.isHidden = false
 
-            if !currentViewController.detailView.isShow() {
+            if metadatas[currentIndex].classFile == NKCommon.TypeClassFile.video.rawValue || metadatas[currentIndex].classFile == NKCommon.TypeClassFile.audio.rawValue {
                 currentViewController.playerToolBar?.show(enableTimerAutoHide: enableTimerAutoHide)
+
             }
 
             NCUtility.shared.colorNavigationController(navigationController, backgroundColor: .systemBackground, titleColor: .label, tintColor: nil, withoutShadow: false)
@@ -229,7 +226,30 @@ class NCViewerMediaPage: UIViewController {
 
     @objc func downloadedFile(_ notification: NSNotification) {
 
+        guard let userInfo = notification.userInfo as NSDictionary?,
+              let ocId = userInfo["ocId"] as? String
+        else {
+            return
+        }
+
         progressView.progress = 0
+        let metadata = metadatas[currentIndex]
+
+        if metadata.ocId == ocId,
+           (metadata.classFile == NKCommon.TypeClassFile.video.rawValue || metadata.classFile == NKCommon.TypeClassFile.audio.rawValue),
+           CCUtility.fileProviderStorageExists(metadata),
+           let ncplayer = currentViewController.ncplayer {
+            let url = URL(fileURLWithPath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!)
+            if ncplayer.isPlay() {
+                ncplayer.playerPause()
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+                    ncplayer.openAVPlayer(url: url)
+                    ncplayer.playerPlay()
+                }
+            } else {
+                ncplayer.openAVPlayer(url: url)
+            }
+        }
     }
 
     @objc func triggerProgressTask(_ notification: NSNotification) {
@@ -346,17 +366,12 @@ class NCViewerMediaPage: UIViewController {
     @objc func showPlayerToolBar(_ notification: NSNotification) {
 
         if let userInfo = notification.userInfo as NSDictionary?, let ocId = userInfo["ocId"] as? String, let enableTimerAutoHide = userInfo["enableTimerAutoHide"] as? Bool {
-            if currentViewController.metadata.ocId == ocId, let playerToolBar = currentViewController.playerToolBar, !playerToolBar.isPictureInPictureActive() {
+            if currentViewController.metadata.ocId == ocId {
                 changeScreenMode(mode: .normal, enableTimerAutoHide: enableTimerAutoHide)
             }
         }
     }
-    
-    @objc func reloadMediaPage(_ notification: NSNotification) {
-        
-        self.reloadCurrentPage()
-    }
-    
+
     @objc func applicationDidBecomeActive(_ notification: NSNotification) {
 
         progressView.progress = 0
@@ -400,22 +415,21 @@ class NCViewerMediaPage: UIViewController {
             MPRemoteCommandCenter.shared().skipForwardCommand.isEnabled = true
             skipForwardCommand = MPRemoteCommandCenter.shared().skipForwardCommand.addTarget { event in
 
-                let seconds = Float64((event as! MPSkipIntervalCommandEvent).interval)
-                self.currentViewController.playerToolBar?.skip(seconds: seconds)
+                let seconds = Int32((event as! MPSkipIntervalCommandEvent).interval)
+                ncplayer.player?.jumpForward(seconds)
                 return.success
             }
 
             MPRemoteCommandCenter.shared().skipBackwardCommand.isEnabled = true
             skipBackwardCommand = MPRemoteCommandCenter.shared().skipBackwardCommand.addTarget { event in
 
-                let seconds = Float64((event as! MPSkipIntervalCommandEvent).interval)
-                self.currentViewController.playerToolBar?.skip(seconds: -seconds)
+                let seconds = Int32((event as! MPSkipIntervalCommandEvent).interval)
+                ncplayer.player?.jumpBackward(seconds)
                 return.success
             }
         }
 
         nowPlayingInfo[MPMediaItemPropertyTitle] = metadata.fileNameView
-        nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = ncplayer.durationTime.seconds
         if let image = currentViewController.image {
             nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in
                 return image
@@ -488,7 +502,6 @@ extension NCViewerMediaPage: UIPageViewControllerDelegate, UIPageViewControllerD
     func reloadCurrentPage() {
 
         let viewerMedia = getViewerMedia(index: currentIndex, metadata: metadatas[currentIndex])
-        viewerMedia.autoPlay = false
         pageViewController.setViewControllers([viewerMedia], direction: .forward, animated: false, completion: nil)
     }
     
@@ -497,7 +510,6 @@ extension NCViewerMediaPage: UIPageViewControllerDelegate, UIPageViewControllerD
         currentIndex = index
 
         let viewerMedia = getViewerMedia(index: currentIndex, metadata: metadatas[currentIndex])
-        viewerMedia.autoPlay = autoPlay
         pageViewController.setViewControllers([viewerMedia], direction: direction, animated: true, completion: nil)
     }
 
@@ -520,11 +532,6 @@ extension NCViewerMediaPage: UIPageViewControllerDelegate, UIPageViewControllerD
     // START TRANSITION
     func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
 
-        // Save time video
-        if let ncplayer = currentViewController.ncplayer, ncplayer.isPlay() {
-            ncplayer.saveCurrentTime()
-        }
-
         guard let nextViewController = pendingViewControllers.first as? NCViewerMedia else { return }
         nextIndex = nextViewController.index
     }
@@ -535,7 +542,7 @@ extension NCViewerMediaPage: UIPageViewControllerDelegate, UIPageViewControllerD
         if completed && nextIndex != nil {
             previousViewControllers.forEach { viewController in
                 let viewerMedia = viewController as! NCViewerMedia
-                viewerMedia.ncplayer?.deactivateObserver()
+                viewerMedia.ncplayer?.playerStop()
             }
             currentIndex = nextIndex!
         }
@@ -586,16 +593,9 @@ extension NCViewerMediaPage: UIGestureRecognizerDelegate {
 
     @objc func didSingleTapWith(gestureRecognizer: UITapGestureRecognizer) {
 
-        if let playerToolBar = currentViewController.playerToolBar, playerToolBar.isPictureInPictureActive() {
-            playerToolBar.pictureInPictureController?.stopPictureInPicture()
-        }
-
         if currentScreenMode == .full {
-
             changeScreenMode(mode: .normal, enableTimerAutoHide: true)
-
         } else {
-
             changeScreenMode(mode: .full)
         }
     }
@@ -608,31 +608,13 @@ extension NCViewerMediaPage: UIGestureRecognizerDelegate {
         if !currentViewController.metadata.livePhoto { return }
 
         if gestureRecognizer.state == .began {
-
-            currentViewController.updateViewConstraints()
-            currentViewController.statusViewImage.isHidden = true
-            currentViewController.statusLabel.isHidden = true
-
             let fileName = (currentViewController.metadata.fileNameView as NSString).deletingPathExtension + ".mov"
             if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileNameView LIKE[c] %@", currentViewController.metadata.account, currentViewController.metadata.serverUrl, fileName)), CCUtility.fileProviderStorageExists(metadata) {
-
                 AudioServicesPlaySystemSound(1519) // peek feedback
-
-                let urlVideo = NCKTVHTTPCache.shared.getVideoURL(metadata: metadata)
-                
-                if let url = urlVideo.url {
-                    self.ncplayerLivePhoto = NCPlayer.init(url: url, autoPlay: true, isProxy: urlVideo.isProxy, imageVideoContainer: self.currentViewController.imageVideoContainer, playerToolBar: nil, metadata: metadata, detailView: nil, viewController: self)
-                    self.ncplayerLivePhoto?.openAVPlayer()
-                }
+                currentViewController.playLivePhoto(filePath: CCUtility.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)!)
             }
-
         } else if gestureRecognizer.state == .ended {
-
-            currentViewController.statusViewImage.isHidden = false
-            currentViewController.statusLabel.isHidden = false
-            currentViewController.imageVideoContainer.image = currentViewController.image
-            ncplayerLivePhoto?.videoLayer?.removeFromSuperlayer()
-            ncplayerLivePhoto?.deactivateObserver()
+            currentViewController.stopLivePhoto()
         }
     }
 }