b面正式版1.0.2

This commit is contained in:
Mr.zhou 2024-05-31 17:05:17 +08:00
parent 3c20413986
commit 62deb230c0
85 changed files with 2184 additions and 697 deletions

View File

@ -42,8 +42,14 @@
CB24169B2C05E34D007877F7 /* MPPositive_LoveArtistTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB24169A2C05E34D007877F7 /* MPPositive_LoveArtistTableViewCell.swift */; };
CB24169D2C05E89C007877F7 /* MPPositive_LoveSongsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB24169C2C05E89C007877F7 /* MPPositive_LoveSongsViewController.swift */; };
CB24169F2C05EF1C007877F7 /* MPPositive_OfflineSongsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB24169E2C05EF1C007877F7 /* MPPositive_OfflineSongsViewController.swift */; };
CB48409C2C08721600341244 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = CB48409B2C08721600341244 /* PrivacyInfo.xcprivacy */; };
CB48409E2C08738700341244 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = CB48409D2C08738700341244 /* GoogleService-Info.plist */; };
CB4840A02C087BD900341244 /* MP_AnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48409F2C087BD900341244 /* MP_AnalyticsManager.swift */; };
CB4840A32C0882D100341244 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = CB4840A22C0882D100341244 /* FirebaseAnalytics */; };
CB4840A52C0882D100341244 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = CB4840A42C0882D100341244 /* FirebaseCrashlytics */; };
CB5661292BE09D0500CFD014 /* MPPositive_JsonPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB5661282BE09D0500CFD014 /* MPPositive_JsonPlayer.swift */; };
CB56612D2BE0DF8C00CFD014 /* MP_WebWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB56612C2BE0DF8C00CFD014 /* MP_WebWork.swift */; };
CBAFC9EF2C09A1FE0054500E /* FirebaseRemoteConfig in Frameworks */ = {isa = PBXBuildFile; productRef = CBAFC9EE2C09A1FE0054500E /* FirebaseRemoteConfig */; };
CBB5D31D2BDF4E9600CC333D /* MPPositive_MusicItemShowTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB5D31C2BDF4E9600CC333D /* MPPositive_MusicItemShowTableViewCell.swift */; };
CBB5D31F2BDF711600CC333D /* MPPositive_SongItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB5D31E2BDF711600CC333D /* MPPositive_SongItemModel.swift */; };
CBB5D3222BDF80C800CC333D /* MPPositive_PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB5D3212BDF80C800CC333D /* MPPositive_PlayerViewController.swift */; };
@ -248,6 +254,9 @@
CB24169A2C05E34D007877F7 /* MPPositive_LoveArtistTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_LoveArtistTableViewCell.swift; sourceTree = "<group>"; };
CB24169C2C05E89C007877F7 /* MPPositive_LoveSongsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_LoveSongsViewController.swift; sourceTree = "<group>"; };
CB24169E2C05EF1C007877F7 /* MPPositive_OfflineSongsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_OfflineSongsViewController.swift; sourceTree = "<group>"; };
CB48409B2C08721600341244 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
CB48409D2C08738700341244 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
CB48409F2C087BD900341244 /* MP_AnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_AnalyticsManager.swift; sourceTree = "<group>"; };
CB5661282BE09D0500CFD014 /* MPPositive_JsonPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_JsonPlayer.swift; sourceTree = "<group>"; };
CB56612C2BE0DF8C00CFD014 /* MP_WebWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MP_WebWork.swift; sourceTree = "<group>"; };
CBB5D31C2BDF4E9600CC333D /* MPPositive_MusicItemShowTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPPositive_MusicItemShowTableViewCell.swift; sourceTree = "<group>"; };
@ -421,7 +430,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CB4840A52C0882D100341244 /* FirebaseCrashlytics in Frameworks */,
CB4840A32C0882D100341244 /* FirebaseAnalytics in Frameworks */,
639E3B772F558B3350DD56BA /* Pods_MusicPlayer.framework in Frameworks */,
CBAFC9EF2C09A1FE0054500E /* FirebaseRemoteConfig in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -456,6 +468,8 @@
009662382BB14A5B00FCA65F /* Assets.xcassets */,
0096623A2BB14A5B00FCA65F /* LaunchScreen.storyboard */,
0096623D2BB14A5B00FCA65F /* Info.plist */,
CB48409D2C08738700341244 /* GoogleService-Info.plist */,
CB48409B2C08721600341244 /* PrivacyInfo.xcprivacy */,
);
path = MusicPlayer;
sourceTree = "<group>";
@ -643,20 +657,20 @@
children = (
CBCB4FAE2BD11402009760B3 /* MPSideA_CenterViewController.swift */,
CBCB4FAF2BD11402009760B3 /* MPSideA_CenterViewController.xib */,
CBCB4FAC2BD11402009760B3 /* MPSideA_AboutViewController.swift */,
CBCB4FAD2BD11402009760B3 /* MPSideA_AboutViewController.xib */,
CBCB4FB02BD11402009760B3 /* MPSideA_DeleteViewController.swift */,
CBCB4FB12BD11402009760B3 /* MPSideA_DeleteViewController.xib */,
CBCB4FB22BD11402009760B3 /* MPSideA_MoreViewController.swift */,
CBCB4FB32BD11402009760B3 /* MPSideA_MoreViewController.xib */,
CBCB4FB42BD11402009760B3 /* MPSideA_PrivacyViewController.swift */,
CBCB4FB52BD11402009760B3 /* MPSideA_PrivacyViewController.xib */,
CBCB4FB02BD11402009760B3 /* MPSideA_DeleteViewController.swift */,
CBCB4FB12BD11402009760B3 /* MPSideA_DeleteViewController.xib */,
CBCB4FB62BD11402009760B3 /* MPSideA_RenameViewController.swift */,
CBCB4FB72BD11402009760B3 /* MPSideA_RenameViewController.xib */,
CBCB4FB82BD11402009760B3 /* MPSideA_ServiceViewController.swift */,
CBCB4FB92BD11402009760B3 /* MPSideA_ServiceViewController.xib */,
CBCB4FBA2BD11402009760B3 /* MPSideA_SettingViewController.swift */,
CBCB4FBB2BD11402009760B3 /* MPSideA_SettingViewController.xib */,
CBCB4FAC2BD11402009760B3 /* MPSideA_AboutViewController.swift */,
CBCB4FAD2BD11402009760B3 /* MPSideA_AboutViewController.xib */,
CBCB4FB82BD11402009760B3 /* MPSideA_ServiceViewController.swift */,
CBCB4FB92BD11402009760B3 /* MPSideA_ServiceViewController.xib */,
CBCB4FB42BD11402009760B3 /* MPSideA_PrivacyViewController.swift */,
CBCB4FB52BD11402009760B3 /* MPSideA_PrivacyViewController.xib */,
);
path = "Center个人资源";
sourceTree = "<group>";
@ -1018,6 +1032,7 @@
CBD958D12BB6600500666B0D /* MP_PlayerSlider.swift */,
CB102F532BFAFA7200E967D8 /* MP_CircularProgressView.swift */,
CB102F542BFAFA7200E967D8 /* MP_DownloadManager.swift */,
CB48409F2C087BD900341244 /* MP_AnalyticsManager.swift */,
);
path = "Tool(工具封装)";
sourceTree = "<group>";
@ -1076,6 +1091,11 @@
dependencies = (
);
name = MusicPlayer;
packageProductDependencies = (
CB4840A22C0882D100341244 /* FirebaseAnalytics */,
CB4840A42C0882D100341244 /* FirebaseCrashlytics */,
CBAFC9EE2C09A1FE0054500E /* FirebaseRemoteConfig */,
);
productName = MusicPlayer;
productReference = 009662292BB14A5A00FCA65F /* MusicPlayer.app */;
productType = "com.apple.product-type.application";
@ -1105,6 +1125,9 @@
Base,
);
mainGroup = 009662202BB14A5A00FCA65F;
packageReferences = (
CB4840A12C0882D100341244 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
);
productRefGroup = 0096622A2BB14A5A00FCA65F /* Products */;
projectDirPath = "";
projectRoot = "";
@ -1129,6 +1152,7 @@
CBC54E572BC4D5D3003B1901 /* Shhh….mp3 in Resources */,
CBCB500D2BD11402009760B3 /* MPSideA_CustomTabBarView.xib in Resources */,
CBCB50072BD11402009760B3 /* MPSideA_PlayerViewController.xib in Resources */,
CB48409E2C08738700341244 /* GoogleService-Info.plist in Resources */,
0096623C2BB14A5B00FCA65F /* LaunchScreen.storyboard in Resources */,
CBC54E5C2BC4D5D3003B1901 /* TV.mp3 in Resources */,
CBCB50152BD11402009760B3 /* MPSideA_Home_FourthListCollectionViewCell.xib in Resources */,
@ -1155,6 +1179,7 @@
CBCB50052BD11402009760B3 /* MPSideA_HomeViewController.xib in Resources */,
CBC54E642BC4D5D3003B1901 /* Seawater Surging.mp3 in Resources */,
CBCB500F2BD11402009760B3 /* MPSideA_CenterTableViewCell.xib in Resources */,
CB48409C2C08721600341244 /* PrivacyInfo.xcprivacy in Resources */,
CBCB4FF32BD11402009760B3 /* MPSideA_AboutViewController.xib in Resources */,
CBC54E582BC4D5D3003B1901 /* Shh Shh.mp3 in Resources */,
CBC54E632BC4D5D3003B1901 /* Howling Wind.mp3 in Resources */,
@ -1300,6 +1325,7 @@
CBE1CB4E2BDE4BD800701D57 /* MPPositive_ListAlbumListViewModel.swift in Sources */,
CB2416972C05D3C3007877F7 /* MPPositive_CollectionArtistViewModel.swift in Sources */,
CBD313572BD63B390015D227 /* MPPositive_HomeListSecondCollectionViewCell.swift in Sources */,
CB4840A02C087BD900341244 /* MP_AnalyticsManager.swift in Sources */,
0096622D2BB14A5A00FCA65F /* AppDelegate.swift in Sources */,
CBC32A532BD8D9F300687171 /* MPPositive_BrowseItemModel.swift in Sources */,
CBE16B952BF251FF005B7EE6 /* MPPositive_SearchSuggestionItemListModel.swift in Sources */,
@ -1522,15 +1548,21 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.2;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = T93S37G27F;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MusicPlayer/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Musicoo;
INFOPLIST_KEY_CFBundleDisplayName = Musiclax;
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "\"Musiclax\" needs to obtain your location information in order to refine the preview music information provided to you!";
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "\"Musiclax\" needs to obtain your location information in order to refine the preview music information provided to you!";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "\"Musiclax\" needs to obtain your location information in order to refine the preview music information provided to you!";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "\"Musiclax\" requires you to turn on the microphone to recognize surrounding decibels and automatically turn on white noise for you. Do you allow this application to obtain your microphone permissions?";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "\"Musiclax\" requires opening your album to obtain photos, which are used to add your custom white noise. Do you want to allow this application to obtain your album permissions?";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
@ -1538,8 +1570,8 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.lux.musicplayer.MusicPlayer;
MARKETING_VERSION = 1.0.2;
PRODUCT_BUNDLE_IDENTIFIER = relax.offline.mp3.music;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@ -1560,15 +1592,21 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1.2;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_TEAM = T93S37G27F;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MusicPlayer/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Musicoo;
INFOPLIST_KEY_CFBundleDisplayName = Musiclax;
INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "\"Musiclax\" needs to obtain your location information in order to refine the preview music information provided to you!";
INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "\"Musiclax\" needs to obtain your location information in order to refine the preview music information provided to you!";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "\"Musiclax\" needs to obtain your location information in order to refine the preview music information provided to you!";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "\"Musiclax\" requires you to turn on the microphone to recognize surrounding decibels and automatically turn on white noise for you. Do you allow this application to obtain your microphone permissions?";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "\"Musiclax\" requires opening your album to obtain photos, which are used to add your custom white noise. Do you want to allow this application to obtain your album permissions?";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UIStatusBarStyle = UIStatusBarStyleLightContent;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
@ -1576,8 +1614,8 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.lux.musicplayer.MusicPlayer;
MARKETING_VERSION = 1.0.2;
PRODUCT_BUNDLE_IDENTIFIER = relax.offline.mp3.music;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@ -1613,6 +1651,35 @@
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
CB4840A12C0882D100341244 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/firebase/firebase-ios-sdk";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 10.27.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
CB4840A22C0882D100341244 /* FirebaseAnalytics */ = {
isa = XCSwiftPackageProductDependency;
package = CB4840A12C0882D100341244 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseAnalytics;
};
CB4840A42C0882D100341244 /* FirebaseCrashlytics */ = {
isa = XCSwiftPackageProductDependency;
package = CB4840A12C0882D100341244 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseCrashlytics;
};
CBAFC9EE2C09A1FE0054500E /* FirebaseRemoteConfig */ = {
isa = XCSwiftPackageProductDependency;
package = CB4840A12C0882D100341244 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseRemoteConfig;
};
/* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */
009662352BB14A5A00FCA65F /* MusicPlayer.xcdatamodeld */ = {
isa = XCVersionGroup;

View File

@ -49,6 +49,16 @@
ReferencedContainer = "container:MusicPlayer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-FIRDebugEnabled"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "-FIRDebugDisabled"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "IDERedirectionPolicy"

View File

@ -0,0 +1,123 @@
{
"originHash" : "c63c63846d9c539229e96de38d6af51417e28c0ee9a0bc48bd0f0f19d923c329",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "748c7837511d0e6a507737353af268484e1745e2",
"version" : "1.2024011601.1"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "076b241a625e25eac22f8849be256dfb960fcdfe",
"version" : "10.19.1"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "8bcaf973b1d84e119b7c7c119abad72ed460979f",
"version" : "10.27.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "70df02431e216bed98dd461e0c4665889245ba70",
"version" : "10.27.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "a637d318ae7ae246b02d7305121275bc75ed5565",
"version" : "9.4.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "57a1d307f42df690fdef2637f3e5b776da02aad6",
"version" : "7.13.3"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359",
"version" : "1.62.2"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "0382ca27f22fb3494cf657d8dc356dc282cd1193",
"version" : "3.4.1"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "2d12673670417654f08f5f90fdd62926dc3a2648",
"version" : "100.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
"version" : "1.26.0"
}
}
],
"version" : 3
}

View File

@ -10,6 +10,7 @@ import CoreData
import AVFoundation
import Alamofire
import Tiercel
import FirebaseCore
@_exported import IQKeyboardManagerSwift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
@ -25,8 +26,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
print("Users are not allowed to be notified of messages.")
}
}
//FireBase
FirebaseApp.configure()
//
DownloadManager.shared.cancelAllTasksIfNeeded()
MP_DownloadManager.shared.cancelAllTasksIfNeeded()
setAudioSupport()
MP_NetWorkManager.shared.requestStatusToYouTube()
IQKeyboardManager.shared.enable = true
@ -34,12 +37,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window = UIWindow(frame: UIScreen.main.bounds)
window?.backgroundColor = .init(hex: "#161616")
switch_lunch()
//
MP_AnalyticsManager.shared.user_launchAction()
return true
}
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
if identifier == "com.yourApp.backgroundDownload" {
DownloadManager.shared.session = SessionManager("com.yourApp.backgroundDownload", configuration: .init())
DownloadManager.shared.session.completionHandler = completionHandler
MP_DownloadManager.shared.session = SessionManager("com.yourApp.backgroundDownload", configuration: .init())
MP_DownloadManager.shared.session.completionHandler = completionHandler
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -1,15 +1,16 @@
{
"images" : [
{
"filename" : "img_v3_02ae_ad486134-21d1-4b05-869c-06e3d548e40g.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "img_v3_02b6_9dd8ad60-2766-4b89-9959-401f232d926g.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "img_v3_02b6_9dd8ad60-2766-4b89-9959-401f232d926g 1.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "img_v3_02ae_c419ebcc-7bcc-4018-a6f4-b4cb25f7d11g.png",
"filename" : "img_v3_02b6_3f726db0-fde6-4335-a78b-2a4ba9365ccg.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "img_v3_02ae_2efa5fb1-c28e-4299-b6c7-abe303cb6f8g.png",
"filename" : "img_v3_02b6_97959922-6ac1-437f-aab5-c65850cdb09g.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "Group_1597880487@2x.png",
"filename" : "Group_1597880484@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Group_1597880487@3x.png",
"filename" : "Group_1597880484@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,22 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "MUSICOO@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "MUSICOO@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyDIsVu-mThBUmZvq6FiVlcq6mTElUJTuhg</string>
<key>GCM_SENDER_ID</key>
<string>773095886766</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>relax.offline.mp3.music</string>
<key>PROJECT_ID</key>
<string>musiclax-ios</string>
<key>STORAGE_BUCKET</key>
<string>musiclax-ios.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:773095886766:ios:54ac9cf528ca696540823f</string>
</dict>
</plist>

View File

@ -2,39 +2,14 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>NSMicrophoneUsageDescription</key>
<string>&quot;Musicoo&quot; requires you to turn on the microphone to recognize surrounding decibels and automatically turn on white noise for you. Do you allow this application to obtain your microphone permissions?</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>&quot;Musicoo&quot; requires opening your album to obtain photos, which are used to add your custom white noise. Do you want to allow this application to obtain your album permissions?</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
<string>backgroundFetch</string>
</array>
<key>NSLocationWhenInUseUsageDescription</key>
<string>&quot;Musicoo&quot; needs to obtain your location information in order to refine the preview music information provided to you!</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>&quot;Musicoo&quot; needs to obtain your location information in order to refine the preview music information provided to you!</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>&quot;Musicoo&quot; needs to obtain your location information in order to refine the preview music information provided to you!</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
<key>CFBundleURLName</key>
<string>com.lux.musicplayer.MusicPlayer</string>
</dict>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>backgroundFetch</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSUserTrackingUsageDescription</key>
<string>&quot;Musiclax&quot; needs to request tracking permissions to provide a personalized advertising experience. We respect and protect your privacy and will not sell your data to third parties.</string>
</dict>
</plist>

View File

@ -16,9 +16,11 @@ class MP_LunchViewController: UIViewController {
//
private var timer:CADisplayLink!
//
private lazy var maxTimes:TimeInterval = 8
private lazy var maxTimes:TimeInterval = 6
//
private lazy var currentTimes:TimeInterval = 0
//
private var completionBlock:(() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .init(hex: "#000000")
@ -27,14 +29,60 @@ class MP_LunchViewController: UIViewController {
timer.preferredFramesPerSecond = 20
//线
timer.add(to: RunLoop.current, forMode: .common)
//
timer.isPaused = false
//idfa
_ = requestTrackingAuthorization(self)
//
MP_LocationManager.shared.setLocationPermission(self, complete: nil)
//youtube
MP_WebWork.shared.pingYoutubeHome()
NotificationCenter.notificationKey.add(observer: self, selector: #selector(jumpAction(_:)), notificationName: .js_edit_completion)
MP_AnalyticsManager.shared.getOpenStatus { [weak self] open in
guard let self = self else {return}
if open {
//ip
MP_NetWorkManager.shared.requestIPInfo { statu in
if statu == true {
//b
print("BLog")
self.completionBlock = {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_positive()
}
}
}else {
print("ALog")
//A
self.completionBlock = {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_aSide()
}
}
}
}
}else {
print("ALog")
//A
completionBlock = {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_aSide()
}
}
}
}
//
timer.isPaused = false
}
deinit {
//
@ -42,15 +90,9 @@ class MP_LunchViewController: UIViewController {
timer = nil
NotificationCenter.default.removeObserver(self)
}
@objc private func jumpAction(_ sender:Notification) {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//
timer.isPaused = true
//
accessAppdelegate.switch_positive()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
MP_AnalyticsManager.shared.launch_pvAction()
}
//
@ -66,14 +108,9 @@ class MP_LunchViewController: UIViewController {
progressView.setProgress(value)
}
}else {
// DispatchQueue.main.async {
// [weak self] in
// guard let self = self else {return}
// //
// timer.isPaused = true
// //
// accessAppdelegate.switch_positive()
// }
if completionBlock != nil {
completionBlock!()
}
}
}
}

View File

@ -118,7 +118,7 @@ extension UIImagePickerControllerDelegate where Self:UIViewController{
private func album(){
DispatchQueue.main.async {
//
let alertController = UIAlertController(title: "Access album permission request", message: "“Musicoo”currently does not have permission to access the album and cannot obtain album data. If you want to use the photo album function, please click “Settings” to allow this App to obtain permission to access the photo album", preferredStyle: .alert)
let alertController = UIAlertController(title: "Access album permission request", message: "“Musiclax”currently does not have permission to access the album and cannot obtain album data. If you want to use the photo album function, please click “Settings” to allow this App to obtain permission to access the photo album", preferredStyle: .alert)
let CancelAction = UIAlertAction(title:"Cancel", style: .cancel) { (Cancel) in
}
@ -141,7 +141,7 @@ extension UIImagePickerControllerDelegate where Self:UIViewController{
private func camera(){
DispatchQueue.main.async {
//
let alertController = UIAlertController(title: "Access camera permission request", message: "“Musicoo”does not have access to the camera, and cannot call camera functions. If you want to use the camera function, please click “Settings” to allow this App to gain access to the camera", preferredStyle: .alert)
let alertController = UIAlertController(title: "Access camera permission request", message: "“Musiclax”does not have access to the camera, and cannot call camera functions. If you want to use the camera function, please click “Settings” to allow this App to gain access to the camera", preferredStyle: .alert)
let CancelAction = UIAlertAction(title:"Cancel", style: .cancel) { (Cancel) in
}
@ -164,7 +164,7 @@ extension UIImagePickerControllerDelegate where Self:UIViewController{
private func video(){
DispatchQueue.main.async {
//
let alertController = UIAlertController(title: "Access album permission request", message: "“Musicoo” needs to open your album to get the photos that will be used to add your custom white noise. Please go to ”Settings“ to turn on this permission!", preferredStyle: .alert)
let alertController = UIAlertController(title: "Access album permission request", message: "“Musiclax” needs to open your album to get the photos that will be used to add your custom white noise. Please go to ”Settings“ to turn on this permission!", preferredStyle: .alert)
let CancelAction = UIAlertAction(title:"Cancel", style: .cancel) { (Cancel) in
}

View File

@ -95,6 +95,8 @@ extension NotificationCenter{
case positive_nav_push
///bPop
case positive_nav_pop
///b
case netWork_error_deal
}
}
}

View File

@ -7,6 +7,8 @@
import UIKit
import Foundation
import AVFoundation
import AppTrackingTransparency
import AdSupport
@_exported import JXSegmentedView
@_exported import JXPagingView
//JXPagingListContainerViewextensionJXSegmentedViewListContainer
@ -38,15 +40,22 @@ let Phone_Model = UIDevice.current.model
let System_Version = UIDevice.current.systemVersion
///
let Language_first_local = NSLocale.preferredLanguages.first!
///
var app_Version:String{
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
return version
}else {
return "1.0.0"
}
}
///
let bottomPadding = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
///
let placeholderImage:UIImage = UIImage(named: "Home First'placeholder")!
///
let privacyUrl:URL = .init(string: "https://musicoo.app/privacy")!
let privacyUrl:URL = .init(string: "https://musiclax.mystrikingly.com/privacy")!
///
let serviceUrl:URL = .init(string: "https://musicoo.app/terms")!
let serviceUrl:URL = .init(string: "https://musiclax.mystrikingly.com/terms")!
//MARK: -
///
@ -71,6 +80,20 @@ func getDocumentsFileURL(_ videoID: String) -> String? {
return nil
}
}
///nextIDID
func improveDataforLycirsAndRelated(_ song:MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) {
//next
MP_NetWorkManager.shared.requestNextLyricsAndRelated(song){ result in
completion(result)
}
}
///player
func improveDataforResouceAndCover(_ song:MPPositive_SongItemModel, completion:@escaping((([String],[Int],[String]), [String]?) -> Void)) {
//player
MP_NetWorkManager.shared.requestAndroidPlayer(song.videoId, playlistId: "") { resourceUrls, coverUrls in
completion(resourceUrls,coverUrls)
}
}
///
func setTimesToMinSeconds(_ time:TimeInterval) -> String {
//
@ -99,7 +122,7 @@ func authorize(observe:UIViewController) -> Bool{
})
default: ()
DispatchQueue.main.async(execute: { () -> Void in
let alertController = UIAlertController(title: "Get Microphone Access",message: "“Musicoo” asks you to turn on your microphone to recognize the decibels around you and turns on white noise for you automatically. Please go to the “Settings” page to turn on the microphone permission",preferredStyle: .alert)
let alertController = UIAlertController(title: "Get Microphone Access",message: "“Musiclax” asks you to turn on your microphone to recognize the decibels around you and turns on white noise for you automatically. Please go to the “Settings” page to turn on the microphone permission",preferredStyle: .alert)
let cancelAction = UIAlertAction(title:"Cancel", style: .cancel, handler:nil)
let settingsAction = UIAlertAction(title:"Settings", style: .default, handler: {
(action) -> Void in
@ -143,3 +166,41 @@ func switchPlayTypeBtnIcon(_ btn:UIButton) {
btn.setBackgroundImage(UIImage(named: "Player_Single'logo"), for: .normal)
}
}
///广
func requestTrackingAuthorization(_ observe:UIViewController) -> Bool {
if #available(iOS 14, *) {
//
let status = ATTrackingManager.trackingAuthorizationStatus
switch status {
case .notDetermined:
//
print("未知的跟踪状态")
ATTrackingManager.requestTrackingAuthorization { status in
let isAuthorized = status == .authorized
DispatchQueue.main.async {
_ = requestTrackingAuthorization(observe)
}
}
case .authorized:
// IDFA
print("用户授权跟踪")
return true
case .denied:
print("用户拒绝跟踪")
default:()
print("跟踪状态受限")
}
return false
} else {
return true
}
}
///IDFA
func getIDFA(_ observe:UIViewController) -> UUID? {
if requestTrackingAuthorization(observe) {
let idfa = ASIdentifierManager.shared().advertisingIdentifier
return idfa
}else {
return nil
}
}

View File

@ -15,7 +15,7 @@ class MPPositive_Debouncer: NSObject {
private var delay: TimeInterval
private override init() {
delay = 0.4
delay = 0.5
super.init()
}
deinit {

View File

@ -472,7 +472,6 @@ class MPSideA_MediaCenterManager {
center!.playCommand.addTarget(handler: { [weak self] (event) in
guard let self = self else { return .noActionableNowPlayingItem}
if self.music != nil {
return .success
}else {
return .noActionableNowPlayingItem

View File

@ -0,0 +1,257 @@
//
// MP_AnalyticsManager.swift
// MusicPlayer
//
// Created by Mr.Zhou on 2024/5/30.
//
import UIKit
import FirebaseAnalytics
import FirebaseCrashlytics
import FirebaseRemoteConfig
///
class MP_AnalyticsManager: NSObject {
static let shared = MP_AnalyticsManager()
//MARK: -
private let remoteConfig = RemoteConfig.remoteConfig()
//MARK: -
//
private let user_launch:String = "user_launch"
//app退
private let app_crash:String = "app_crash"
//
private let launch_pv:String = "launch_pv"
//A
private let home_a_pv:String = "home_a_pv"
//B
private let home_b_pv:String = "home_b_pv"
//
private let home_b_module_showsucces_action:String = "home_b_module_showsucces_action"
//
private let home_b_module_click:String = "home_b_module_click"
//B
private let me_b_pv = "me_b_pv"
//B
private let player_b_pv = "player_b_pv"
//
private let player_b_delay_action = "player_b_delay_action"
//
private let player_b_success_action = "player_b_success_action"
//
private let player_b_love_click = "player_b_love_click"
//
private let player_b_unlove_click = "player_b_unlove_click"
//
private let player_b_download_click = "player_b_download_click"
//
private let player_b_downloadsuccess_action = "player_b_downloadsuccess_action"
//B
private let search_pv = "search_pv"
//SUG
private let search_sug_show = "search_sug_show"
//sug
private let search_sug_click = "search_sug_click"
//
private let search_result_pv = "search_result_pv"
//
private let search_resultsuccess_action = "search_resultsuccess_action"
private override init() {
super.init()
Crashlytics.crashlytics().log(app_crash)
//
// remoteConfig.setDefaults(["openStatus":false as NSObject])
}
//MARK: - A/B
func getOpenStatus(_ completion:@escaping ((Bool) -> Void)) {
//
let duration:TimeInterval = 0
remoteConfig.fetch(withExpirationDuration: duration) { status, error in
if status == .success {
//
self.remoteConfig.activate { changed, error in
if error == nil{
let js = self.remoteConfig.configValue(forKey: "openStatus").jsonValue as! [String:Any]
let value = js["versionCode"] as! String
if value == app_Version {
//
let open = js["enter"] as! Bool
if open {
//b
completion(true)
}else {
//a
completion(false)
}
}else {
//B
completion(true)
}
}
}
}else {
//
if let js = self.remoteConfig.configValue(forKey: "openStatus").jsonValue as? [String:Any] {
//
let value = js["versionCode"] as! String
if value == app_Version {
//
let open = js["enter"] as! Bool
if open {
//b
completion(true)
}else {
//a
completion(false)
}
}else {
//B
completion(true)
}
}else {
//A
completion(false)
}
}
}
}
//MARK: -
///
func user_launchAction(){
Analytics.logEvent(user_launch, parameters: nil)
}
///
func launch_pvAction(){
Analytics.logEvent(launch_pv, parameters: nil)
}
///A
func home_a_pvAction(){
Analytics.logEvent(home_a_pv, parameters: nil)
}
///B
func home_b_pvAction(){
Analytics.logEvent(home_b_pv, parameters: nil)
}
///
func home_b_module_showsucces_actionAction(){
Analytics.logEvent(home_b_module_showsucces_action, parameters: nil)
}
///
/// - Parameter modulename:
func home_b_module_clickAction(_ modulename:String){
Analytics.logEvent(home_b_module_click, parameters: ["modulename":modulename])
}
///B
func me_b_pvAction(){
Analytics.logEvent(me_b_pv, parameters: nil)
}
/// B
/// - Parameters:
/// - videoid: id
/// - videoname:
/// - artistname:
func player_b_pvAction(_ videoid:String, videoname:String, artistname:String){
Analytics.logEvent(player_b_pv, parameters: [
"videoid":videoid,
"videoname":videoname,
"artistname":artistname
])
}
///
/// - Parameters:
/// - videoid: id
/// - videoname:
/// - artistname:
func player_b_delay_actionAction(_ videoid:String, videoname:String, artistname:String, delay:String){
Analytics.logEvent(player_b_delay_action, parameters: [
"videoid":videoid,
"videoname":videoname,
"artistname":artistname,
"delay":delay
])
}
///
/// - Parameters:
/// - videoid: id
/// - videoname:
/// - artistname:
func player_b_success_actionAction(_ videoid:String, videoname:String, artistname:String){
Analytics.logEvent(player_b_success_action, parameters: [
"videoid":videoid,
"videoname":videoname,
"artistname":artistname
])
}
///
/// - Parameters:
/// - videoid: id
/// - videoname:
/// - artistname:
func player_b_love_clickAction(_ videoid:String, videoname:String, artistname:String){
Analytics.logEvent(player_b_love_click, parameters: [
"videoid":videoid,
"videoname":videoname,
"artistname":artistname
])
}
///
/// - Parameters:
/// - videoid: id
/// - videoname:
/// - artistname:
func player_b_unlove_clickAction(_ videoid:String, videoname:String, artistname:String){
Analytics.logEvent(player_b_unlove_click, parameters: [
"videoid":videoid,
"videoname":videoname,
"artistname":artistname
])
}
///
/// - Parameters:
/// - videoid: id
/// - videoname:
/// - artistname:
func player_b_download_clickAction(_ videoid:String, videoname:String, artistname:String){
Analytics.logEvent(player_b_download_click, parameters: [
"videoid":videoid,
"videoname":videoname,
"artistname":artistname
])
}
///
/// - Parameters:
/// - videoid: id
/// - videoname:
/// - artistname:
func player_b_downloadsuccess_actionAction(_ videoid:String, videoname:String, artistname:String){
Analytics.logEvent(player_b_downloadsuccess_action, parameters: [
"videoid":videoid,
"videoname":videoname,
"artistname":artistname
])
}
///B
func search_pvAction(){
Analytics.logEvent(search_pv, parameters: nil)
}
///SUG
func search_sug_showAction(){
Analytics.logEvent(search_sug_show, parameters: nil)
}
/// sug
/// - Parameter sugname:
func search_sug_clickAction(_ sugname:String){
Analytics.logEvent(search_sug_click, parameters: ["sugname":sugname])
}
///
func search_result_pvAction(){
Analytics.logEvent(search_result_pv, parameters: nil)
}
///
func search_resultsuccess_actionAction(){
Analytics.logEvent(search_resultsuccess_action, parameters: nil)
}
}

View File

@ -8,14 +8,14 @@
import Foundation
import Foundation
import Tiercel
class DownloadManager: NSObject {
static let shared = DownloadManager()
class MP_DownloadManager: NSObject {
static let shared = MP_DownloadManager()
var session: SessionManager!
var progressHandlers: [URL: (CGFloat) -> Void] = [:]
var progressHandlers: [String: (CGFloat) -> Void] = [:]
var completionHandlers: [URL: (Result<MPPositive_SongItemModel, Error>) -> Void] = [:]
var downloadTasks: [URL: URLSessionDownloadTask] = [:]
var progressStorage: [URL: CGFloat] = [:] //
var progressStorage: [String: CGFloat] = [:] //
var songHandlers:[URL: MPPositive_SongItemModel] = [:]
private override init() {
super.init()
@ -26,15 +26,15 @@ class DownloadManager: NSObject {
session = SessionManager("com.yourApp.backgroundDownload", configuration: configuration)
}
func downloadVideo(from url: URL, song:MPPositive_SongItemModel, progressHandler: @escaping (CGFloat) -> Void, completion: @escaping (Result<MPPositive_SongItemModel, Error>) -> Void) {
progressHandlers[url] = progressHandler
progressHandlers[song.videoId] = progressHandler
completionHandlers[url] = completion
songHandlers[url] = song
let downloadTask = session.download(url, headers: ["Accept-Encoding": "gzip, deflate"])
//
downloadTask?.progress(handler: { [weak self] (task) in
guard let self = self else {return}
progressHandlers[task.url]?(task.progress.fractionCompleted)
progressStorage[task.url] = task.progress.fractionCompleted
progressHandlers[song.videoId]?(task.progress.fractionCompleted)
progressStorage[song.videoId] = task.progress.fractionCompleted
})
//
downloadTask?.success(handler: { [weak self] (task) in
@ -54,15 +54,21 @@ class DownloadManager: NSObject {
}
}
let fileURL = downloadsURL.appendingPathComponent("\(MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId ?? "").mp4")
let fileURL = downloadsURL.appendingPathComponent("\(song.videoId ?? "").mp4")
do {
try FileManager.default.moveItem(at: filePathUrl, to: fileURL)
//VideoID
completionHandlers[originalURL]?(.success(songHandlers[originalURL]!))
progressStorage[originalURL] = nil //
progressStorage[song.videoId] = nil //
} catch {
completionHandlers[originalURL]?(.failure(error))
}
//
if task.status == .succeeded {
session.remove(task, completely: true) {_ in
print("\(song.title ?? "")下载任务完成,移除任务")
}
}
}).failure(handler: { [weak self] (task) in
guard let self = self else {return}
//
@ -70,10 +76,15 @@ class DownloadManager: NSObject {
if let error = task.error {
completionHandlers[originalURL]?(.failure(error))
}
if task.status == .failed {
session.cancel(task) { _ in
print("\(song.title ?? "")下载任务失败,取消任务")
}
}
})
}
func getProgress(for url: URL) -> CGFloat? {
return progressStorage[url]
func getProgress(for videoId: String) -> CGFloat? {
return progressStorage[videoId]
}
func cancelAllTasksIfNeeded() {
//
@ -85,11 +96,12 @@ class DownloadManager: NSObject {
func deleteFileDocuments(_ videoId:String, completion:@escaping((String) -> Void)) {
let downloadsURL = DocumentsURL.appendingPathComponent("Downloads")
let fileURL = downloadsURL.appendingPathComponent("\(videoId).mp4")
if FileManager.default.fileExists(atPath: fileURL.absoluteString) {
if FileManager.default.fileExists(atPath: fileURL.path) {
do{
try FileManager.default.removeItem(at: fileURL)
//
completion(videoId)
print("成功删除了\(videoId)文件")
}catch{
print("删除文件时发生错误:\(error)")
}
@ -98,44 +110,3 @@ class DownloadManager: NSObject {
}
}
}
//class DownloadManager {
//
// static let shared = DownloadManager()
//
// private init() {}
//
// func downloadVideo(from url: URL, videoId: String, progressView: CircularProgressView, completion: @escaping (Result<URL, Error>) -> Void) {
// let destination: DownloadRequest.Destination = { _, _ in
// let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
// let downloadsURL = documentsURL.appendingPathComponent("Downloads")
//
// // Downloads
// if !FileManager.default.fileExists(atPath: downloadsURL.path) {
// do {
// try FileManager.default.createDirectory(at: downloadsURL, withIntermediateDirectories: true, attributes: nil)
// } catch {
// completion(.failure(error))
// return (downloadsURL, [.removePreviousFile, .createIntermediateDirectories])
// }
// }
//
// let fileURL = downloadsURL.appendingPathComponent("\(videoId).mp4")
// return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
// }
//
// AF.download(url, to: destination).downloadProgress { progress in
// progressView.setProgress(to: CGFloat(progress.fractionCompleted))
// }.response { response in
// if let error = response.error {
// completion(.failure(error))
// } else if let filePath = response.fileURL {
// completion(.success(filePath))
// }
// }
// }
//}

View File

@ -22,6 +22,15 @@ class MP_HUD: NSObject {
///
case loading
}
///HUD
static func loading(){
SVProgressHUD.setDefaultStyle(.light)
SVProgressHUD.setDefaultMaskType(.clear)
SVProgressHUD.setBackgroundColor(.init(hex: "#80F988", alpha: 0.1))
SVProgressHUD.setForegroundColor(.init(hex: "#80F988"))
SVProgressHUD.setOffsetFromCenter(.init(horizontal: 0, vertical: 0))
SVProgressHUD.show()
}
///HUD
static func text(_ text:String?,delay:TimeInterval,completion:(() -> Void)?){
showWithStatus(hudStatus: .onlyText, text: text, delay: delay, completion: completion)
@ -56,7 +65,8 @@ class MP_HUD: NSObject {
static func showWithStatus(hudStatus status: status, text: String?, delay: TimeInterval ,completion:(() -> Void)?) {
SVProgressHUD.setDefaultStyle(.light)
SVProgressHUD.setDefaultMaskType(.clear)
SVProgressHUD.setBackgroundColor(.white)
SVProgressHUD.setBackgroundColor(.init(hex: "#80F988", alpha: 0.1))
SVProgressHUD.setForegroundColor(.init(hex: "#80F988"))
SVProgressHUD.setOffsetFromCenter(.init(horizontal: 0, vertical: 0))
switch status {
case .success:
@ -81,4 +91,5 @@ class MP_HUD: NSObject {
completion()
}
}
}

View File

@ -39,7 +39,7 @@ class MP_LocationManager: NSObject {
MP_LocationManager().requestLocationAuthorizaiton()
case .restricted, .denied:
DispatchQueue.main.async {
let alertController = UIAlertController(title: "Location permission request", message: "“Musicoo” needs to obtain your location information in order to refine the preview music information provided to you!", preferredStyle: .alert)
let alertController = UIAlertController(title: "Location permission request", message: "“Musiclax” needs to obtain your location information in order to refine the preview music information provided to you!", preferredStyle: .alert)
let CancelAction = UIAlertAction(title:"Cancel", style: .cancel)
let OKAction = UIAlertAction(title: "Settings", style: .default) { (action) in
let url = URL(string: UIApplication.openSettingsURLString)

View File

@ -21,6 +21,8 @@ class MP_NetWorkManager: NSObject {
///
private let MPSession = Alamofire.Session(interceptor: MP_CustomRetrier())
//MARK: - API
///IP
private let iPInfo:String = "https://api.tikustok.com/app/common/getIPInfo"
///
private let header:String = "https://music.youtube.com"
///
@ -37,6 +39,17 @@ class MP_NetWorkManager: NSObject {
private let search = "/search"
///YouTuBe
private let youTubeKeys:[String] = ["MUSIC_VIDEO_TYPE_ATV","MUSIC_VIDEO_TYPE_OMV","MUSIC_PAGE_TYPE_ALBUM","MUSIC_PAGE_TYPE_ARTIST","MUSIC_PAGE_TYPE_PLAYLIST","MUSIC_PAGE_TYPE_TRACK_LYRICS","MUSIC_PAGE_TYPE_TRACK_RELATED"]
///IP
private let banIPs:[String] = ["CN",
"HK",
"TW",
"JP",
"KR",
"GB",
"CH",
"BE",
"MO",
"SG"]
//
enum NetWorkStatus: String {
case notReachable = "网络不可用"
@ -136,7 +149,7 @@ class MP_NetWorkManager: NSObject {
default://
DispatchQueue.main.async {
//
let alertController = UIAlertController(title: "Access network request", message: "”Musicoo“ needs to be loaded via a network request. Please click “Settings” to allow this application to gain access to the network.", preferredStyle: .alert)
let alertController = UIAlertController(title: "Access network request", message: "”Musiclax“ needs to be loaded via a network request. Please click “Settings” to allow this application to gain access to the network.", preferredStyle: .alert)
let CancelAction = UIAlertAction(title:"Cancel", style: .cancel) { (Cancel) in
}
@ -178,12 +191,50 @@ class MP_NetWorkManager: NSObject {
}
//MARK: - API
extension MP_NetWorkManager {
//MARK: - iP
///IP
func requestIPInfo(_ completion:@escaping((Bool) -> Void)) {
//browse
let path = iPInfo
//url
guard let url = URL(string: path) else {
print("Url is Incorrect")
return
}
requestPostIPInfo(url) { open in
completion(open)
}
}
private func requestPostIPInfo(_ url:URL, completion:@escaping((Bool) -> Void)) {
MPSession.request(url, method: .get, encoding: JSONEncoding.default).responseDecodable(of: JsonIPInfo.self) { [weak self] (response) in
guard let self = self else {return}
switch response.result {
case .success(let value):
guard let data = value.data, let code = data.isoCode else {
return
}
if banIPs.contains(code) == true {
//
completion(false)
}else {
//
completion(true)
}
case .failure(let error):
//
handleError(url, error: error)
completion(true)
}
}
}
//MARK: -
///YouTubemusic/
func requestBrowseDatas() {
//continuationcontinuation,
//
browseQueque = DispatchQueue(label: "com.request.browseQueque")
visitorData = nil
//browse
let path = header+point+browse
//url
@ -239,7 +290,8 @@ extension MP_NetWorkManager {
}
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
browseQueque = nil
}
}
}
@ -265,7 +317,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -301,7 +353,7 @@ extension MP_NetWorkManager {
comletion(list)
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -322,7 +374,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -356,7 +408,7 @@ extension MP_NetWorkManager {
comletion(artist)
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -378,7 +430,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -407,7 +459,7 @@ extension MP_NetWorkManager {
}
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -431,7 +483,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -460,7 +512,7 @@ extension MP_NetWorkManager {
}
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -485,7 +537,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -514,7 +566,7 @@ extension MP_NetWorkManager {
completion(listSongs)
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -536,7 +588,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -564,7 +616,7 @@ extension MP_NetWorkManager {
completion(result)
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -592,6 +644,12 @@ extension MP_NetWorkManager {
"platform":"MOBILE",
"browserVersion":"125.0.0.0",
// "userAgent":
// "clientName": "ANDROID_MUSIC",
// "clientVersion": "5.28.1",
// "platform": "MOBILE",
// "androidSdkVersion":"30",
// "userAgent": "com.google.android.apps.youtube.music/5.28.1 (Linux; U; Android 11) gzip"
]
]
]
@ -612,7 +670,7 @@ extension MP_NetWorkManager {
}
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -631,7 +689,7 @@ extension MP_NetWorkManager {
// "context":[
// "client":[
// "clientName": "WEB_REMIX",
//// "visitorData":visitorData,
//// //"visitorData":visitorData,
//// "originalUrl":"https://music.youtube.com/watch?v=\(videoId)",
// //访
// "clientVersion": "1.\(currTimeDate).01.00",
@ -689,7 +747,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -715,7 +773,7 @@ extension MP_NetWorkManager {
completion(parsingLyrics(value) ?? "")
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -736,7 +794,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -765,7 +823,7 @@ extension MP_NetWorkManager {
}
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -789,7 +847,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -817,7 +875,7 @@ extension MP_NetWorkManager {
}
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -840,7 +898,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -870,7 +928,7 @@ extension MP_NetWorkManager {
}
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -897,7 +955,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -926,7 +984,7 @@ extension MP_NetWorkManager {
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
@ -954,7 +1012,7 @@ extension MP_NetWorkManager {
"client":[
//web
"clientName": "WEB_REMIX",
"visitorData":visitorData,
//"visitorData":visitorData,
//访
"clientVersion": "1.\(currTimeDate).01.00",
"platform":"MOBILE",
@ -983,11 +1041,39 @@ extension MP_NetWorkManager {
case .failure(let error):
//
print("Request failed: \(error)")
handleError(url, error: error)
}
}
}
///
private func handleError(_ url: URL, error:AFError) {
//
if let statusCode = error.responseCode {
switch statusCode {
case 400...499:
print("\(url)请求错误,错误码: \(statusCode)")
case 500...599:
print("\(url)服务器错误,错误码: \(statusCode)")
default:
print("\(url)其他 HTTP 错误,错误码: \(statusCode)")
}
} else if let underlyingError = error.underlyingError as? URLError {
switch underlyingError.code {
case .notConnectedToInternet:
print("\(url)网络连接不可用,请检查你的网络设置。")
case .timedOut:
print("\(url)请求超时,请稍后重试。")
default:
print("\(url)NSURL 错误,错误码: \(underlyingError.code.rawValue)")
}
} else {
print("\(url)未知错误: \(error.localizedDescription)")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
//
NotificationCenter.notificationKey.post(notificationName: .netWork_error_deal)
}
}
}
//MARK: -
extension MP_NetWorkManager {
@ -1819,30 +1905,35 @@ extension MP_NetWorkManager {
//MARK: -
///
class MP_CustomRetrier: RequestInterceptor {
//
private let maximumRetryCount = 3
//
//
private var retryCounts: [String: Int] = [:]
func retry(_ request: Alamofire.Request, for session: Alamofire.Session, dueTo error: any Error, completion: @escaping (Alamofire.RetryResult) -> Void) {
// id
let requestID = request.id
let currentRetryCount = retryCounts[requestID.uuidString] ?? 0
//
if currentRetryCount < maximumRetryCount, let response = request.task?.response as? HTTPURLResponse, response.statusCode == 503 {
//
retryCounts[requestID.uuidString] = currentRetryCount + 1
// 1
completion(.retryWithDelay(1))
print("请求\(requestID.uuidString)执行重复请求")
} else {
//
retryCounts[requestID.uuidString] = nil
//
private let maxRetryCount: Int = 3
//
private let retryInterval: TimeInterval = 1.0
//
func retry(_ request: Request, for session: Session, dueTo error: any Error, completion: @escaping (RetryResult) -> Void) {
// URL便
guard let url = request.request?.url?.absoluteString else {
completion(.doNotRetry)
return
}
// URL
let currentRetryCount = retryCounts[url] ?? 0
//
if currentRetryCount < maxRetryCount {
//
retryCounts[url] = currentRetryCount + 1
print("进行对\(url)访问的第\(currentRetryCount)次重试")
// Alamofire
completion(.retryWithDelay(retryInterval))
} else {
//
completion(.doNotRetry)
print("访问\(url)失败,不在进行重试")
// //
// retryCounts[url] = nil
}
}
//
func requestDidFinish(_ request: Request) {
let requestID = request.id
retryCounts[requestID.uuidString] = nil
}
}

View File

@ -10,6 +10,7 @@ import AVFoundation
import MediaPlayer
import AVKit
import FreeStreamer
import Kingfisher
///
enum MP_PlayerStateType:Int {
///
@ -28,6 +29,13 @@ enum MP_PlayerPlayType:Int {
///
case single = 2
}
///
enum MP_TimerStateType:Int {
///
case Resume = 0
///
case Suspend = 1
}
///
typealias MP_PlayTimerStartAction = () -> Void
@ -51,6 +59,14 @@ class MP_PlayerManager:NSObject{
static let shared = MP_PlayerManager()
///
private var player:AVPlayer = AVPlayer()
//
private var center:MPRemoteCommandCenter?
///
private var timer:DispatchSourceTimer?
///
private var times:TimeInterval = 0
///
private var queue:DispatchQueue?
///load
var loadPlayer:MPPositive_PlayerLoadViewModel!{
didSet{
@ -94,6 +110,8 @@ class MP_PlayerManager:NSObject{
}
}
///
private var timerType:MP_TimerStateType = .Suspend
///
private var startActionBlock:MP_PlayTimerStartAction!
///
@ -102,12 +120,50 @@ class MP_PlayerManager:NSObject{
var cacheValueBlock:MP_PlayCacheValueAction!
private override init() {
super.init()
//
timer?.cancel()
queue = DispatchQueue(label: "com.playerTimer.timer",attributes: .concurrent)
timer = DispatchSource.makeTimerSource(queue: queue)
//0.1,0.01
timer?.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(10))
//
timer?.setEventHandler(handler: { [weak self] in
//0.1
self?.times += 0.1
})
//
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(_ :)), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
//
NotificationCenter.notificationKey.add(observer: self, selector: #selector(userSwitchCurrentVideoAction(_ :)), notificationName: .positive_player_reload)
//
NotificationCenter.notificationKey.add(observer: self, selector: #selector(netWorkReachableAction(_ :)), notificationName: .net_switch_reachable)
//
let interval:CMTime = .init(seconds: 1, preferredTimescale: .init(1))
//线
player.addPeriodicTimeObserver(forInterval: interval, queue: .main, using: { [weak self] (time) in
guard let self = self else { return }
cacheLoadTimes()
//
let currentDuration = CMTimeGetSeconds(time)
//
let maxDuration = getMusicDuration()
if maxDuration.isNaN == false {
//
if currentDuration <= maxDuration {
//
if runActionBlock != nil {
runActionBlock!(currentDuration, maxDuration)
}
}
}
})
}
deinit {
NotificationCenter.default.removeObserver(self)
center?.playCommand.removeTarget(self)
center?.pauseCommand.removeTarget(self)
center?.nextTrackCommand.removeTarget(self)
center?.previousTrackCommand.removeTarget(self)
}
///
/// - Parameters:
@ -133,35 +189,62 @@ class MP_PlayerManager:NSObject{
if startAction != nil {
startActionBlock = startAction
}
//
let newItem = loadPlayer.currentVideo.resourcePlayerItem
//playerItem
player.replaceCurrentItem(with: loadPlayer.currentVideo.resourcePlayerItem)
//0
player.seek(to: .zero)
//
let interval:CMTime = .init(seconds: 1, preferredTimescale: .init(1))
//线
player.addPeriodicTimeObserver(forInterval: interval, queue: .main, using: { [weak self] (time) in
guard let self = self else { return }
//
let currentDuration = CMTimeGetSeconds(time)
//
let maxDuration = getMusicDuration()
if maxDuration.isNaN == false {
//
if currentDuration <= maxDuration {
//
if runActionBlock != nil {
runActionBlock!(currentDuration, maxDuration)
}
}
}
})
player.replaceCurrentItem(with: newItem)
if center == nil {
setCommandCenter()
}
//
startTimer()
//PlayerItem
//
loadPlayer.currentVideo?.resourcePlayerItem?.addObserver(self, forKeyPath: "status", options: [.old,.new], context: nil)
//
loadPlayer.currentVideo?.resourcePlayerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.old,.new], context: nil)
if loadPlayer.currentVideo?.isKVO == false {
//
newItem?.addObserver(self, forKeyPath: "status", options: [.old,.new], context: nil)
//
newItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: [.old,.new], context: nil)
//
newItem?.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: [.old,.new], context: nil)
loadPlayer.currentVideo.isKVO = true
//0
player.seek(to: .zero)
updateNowPlayingInfo()
}
}
///
func startTimer() {
guard timerType == .Suspend else {
return
}
times = 0
//
timer?.resume()
timerType = .Resume
}
///
func suspendTimer() {
guard timerType == .Resume else {
return
}
//
timer?.suspend()
timerType = .Suspend
}
///
@objc private func netWorkReachableAction(_ sender:Notification) {
//
if loadPlayer?.currentVideo != nil {
//
let currentTime = loadPlayer.currentVideo!.resourcePlayerItem.currentTime()
//
player.seek(to: currentTime)
player.play()
playState = .Playing
}
}
//KVO
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath else {
@ -175,48 +258,58 @@ class MP_PlayerManager:NSObject{
if playState != .Playing {
//statuVlaueplayerItem
print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 已经准备好播放")
}
}else {
print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 未做好准备播放,失败原因是\(loadPlayer.currentVideo?.resourcePlayerItem.error?.localizedDescription ?? "")")
//
play()
}
case "loadedTimeRanges"://
cacheLoadTimes()
case "playbackLikelyToKeepUp"://
if let playbackLikelyToKeepUp = change?[.newKey] as? Bool, playbackLikelyToKeepUp == true {
if playState != .Playing {
//
print("开始播放音乐-\(loadPlayer.currentVideo?.title ?? "")")
player.play()
playState = .Playing
//
suspendTimer()
let times = Int(self.times)
let msTimes = times*1000
MP_AnalyticsManager.shared.player_b_delay_actionAction(loadPlayer.currentVideo?.song.videoId ?? "", videoname: loadPlayer.currentVideo?.title ?? "", artistname: loadPlayer.currentVideo?.song.shortBylineText ?? "", delay: "\(msTimes)ms")
MP_AnalyticsManager.shared.player_b_success_actionAction(loadPlayer.currentVideo?.song.videoId ?? "", videoname: loadPlayer.currentVideo?.title ?? "", artistname: loadPlayer.currentVideo?.song.shortBylineText ?? "")
//
if startActionBlock != nil {
startActionBlock!()
}
}
}else {
print("当前音乐-\(loadPlayer.currentVideo?.title ?? "") 未做好准备播放,失败原因是\(loadPlayer.currentVideo?.resourcePlayerItem.error?.localizedDescription ?? "")")
//
loadPlayer.remakeImproveData {
[weak self] in
guard let self = self else {return}
//
play()
}
//
player.pause()
playState = .Null
}
case "loadedTimeRanges"://
//Item
if let timeRanges = loadPlayer.currentVideo?.resourcePlayerItem.loadedTimeRanges.map({$0.timeRangeValue}), let first = timeRanges.first {
//
let startSeconds = first.start.seconds
//
let durationSeconds = first.duration.seconds
//
let bufferedSeconds = startSeconds + durationSeconds
//
let maxDuration = getMusicDuration()
//
if cacheValueBlock != nil {
cacheValueBlock!(bufferedSeconds, maxDuration)
}
}
case "playbackLikelyToKeepUp"://
break
default:
break
}
}
//
private func cacheLoadTimes() {
//Item
if let timeRanges = loadPlayer.currentVideo?.resourcePlayerItem.loadedTimeRanges.map({$0.timeRangeValue}), let first = timeRanges.first {
//
let startSeconds = first.start.seconds
//
let durationSeconds = first.duration.seconds
//
let bufferedSeconds = startSeconds + durationSeconds
//
let maxDuration = getMusicDuration()
//
if cacheValueBlock != nil {
cacheValueBlock!(bufferedSeconds, maxDuration)
}
}
}
//MARK: -
///
private func getMusicDuration() -> TimeInterval {
@ -325,7 +418,7 @@ class MP_PlayerManager:NSObject{
switch playType {
case .random://
for (index, item) in loadPlayer.randomVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo.song.videoId {
if item.videoId == loadPlayer.currentVideo?.song.videoId {
//
nextIndex = index - 1
}
@ -334,15 +427,15 @@ class MP_PlayerManager:NSObject{
if nextIndex < 0 {
//
let last = loadPlayer.randomVideos.last
loadPlayer.improveData(last?.videoId ?? "")
loadPlayer.improveData(last?.videoId ?? "", isRandom: true)
}else {
//
let song = loadPlayer.randomVideos[nextIndex]
loadPlayer.improveData(song.videoId ?? "")
loadPlayer.improveData(song.videoId ?? "", isRandom: true)
}
default://
for (index, item) in loadPlayer.songVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo.song.videoId {
if item.videoId == loadPlayer.currentVideo?.song.videoId {
//
nextIndex = index - 1
}
@ -367,7 +460,7 @@ class MP_PlayerManager:NSObject{
switch playType {
case .random:
for (index, item) in loadPlayer.randomVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo.song.videoId {
if item.videoId == loadPlayer.currentVideo?.song.videoId {
//
nextIndex = index + 1
}
@ -376,15 +469,15 @@ class MP_PlayerManager:NSObject{
if nextIndex > (loadPlayer.randomVideos.count-1) {
//
let first = loadPlayer.randomVideos.first
loadPlayer.improveData(first?.videoId ?? "")
loadPlayer.improveData(first?.videoId ?? "", isRandom: true)
}else {
//,ID
let song = loadPlayer.randomVideos[nextIndex]
loadPlayer.improveData(song.videoId ?? "")
loadPlayer.improveData(song.videoId ?? "", isRandom: true)
}
default:
for (index, item) in loadPlayer.songVideos.enumerated() {
if item.videoId == loadPlayer.currentVideo.song.videoId {
if item.videoId == loadPlayer.currentVideo?.song.videoId {
//
nextIndex = index + 1
}
@ -409,10 +502,16 @@ class MP_PlayerManager:NSObject{
player.pause()
//
if let video = sender.object as? MPPositive_SongViewModel {
//KVO
video.resourcePlayerItem.removeObserver(self, forKeyPath: "status")
video.resourcePlayerItem.removeObserver(self, forKeyPath: "loadedTimeRanges")
// video.resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
if video.isKVO == true {
//KVO
video.resourcePlayerItem.removeObserver(self, forKeyPath: "status")
video.resourcePlayerItem.removeObserver(self, forKeyPath: "loadedTimeRanges")
video.resourcePlayerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
video.isKVO = false
}
}
if cacheValueBlock != nil {
cacheValueBlock!(0, 1)
}
if loadPlayer.currentVideo != nil {
//
@ -450,4 +549,88 @@ class MP_PlayerManager:NSObject{
endAction!()
}
}
//MARK: -
private func setCommandCenter() {
//
center = MPRemoteCommandCenter.shared()
//
//
center!.playCommand.addTarget(handler: { [weak self] (event) in
guard let self = self else { return .noActionableNowPlayingItem}
if loadPlayer.currentVideo != nil {
resume()
return .success
}else {
return .noActionableNowPlayingItem
}
})
//
center!.pauseCommand.addTarget(handler: { [weak self] (event) in
guard let self = self else { return .noActionableNowPlayingItem}
if loadPlayer.currentVideo != nil {
pause()
return .success
}else {
return .noActionableNowPlayingItem
}
})
//
center!.previousTrackCommand.addTarget { [weak self] (event) in
guard let self = self else { return .noActionableNowPlayingItem}
if loadPlayer.currentVideo != nil {
previousEvent()
return .success
}else {
return .noActionableNowPlayingItem
}
}
//
center!.nextTrackCommand.addTarget { [weak self] (event) in
guard let self = self else { return .noActionableNowPlayingItem}
if loadPlayer.currentVideo != nil {
nextEvent()
return .success
}else {
return .noActionableNowPlayingItem
}
}
}
//
func updateNowPlayingInfo() {
//info
var info = [String:Any]()
//
info[MPMediaItemPropertyTitle] = loadPlayer.currentVideo?.title ?? ""
//
info[MPMediaItemPropertyArtist] = loadPlayer.currentVideo?.song?.shortBylineText ?? ""
//
info[MPMediaItemPropertyAlbumTitle] = loadPlayer.currentVideo?.song?.longBylineText
//
info[MPMediaItemPropertyPlaybackDuration] = getMusicDuration()
let reviewURL = URL(string: loadPlayer.currentVideo?.song?.reviewUrls?.last ?? "")!
KingfisherManager.shared.retrieveImage(with: reviewURL) { result in
switch result {
case .success(let imageResult):
let image = imageResult.image
info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size, requestHandler: { size in
return image
})
// MPNowPlayingInfoCenter线
DispatchQueue.main.async {
//
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}
case .failure(_):
info[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: placeholderImage.size, requestHandler: { size in
return placeholderImage
})
// MPNowPlayingInfoCenter线
DispatchQueue.main.async {
//
MPNowPlayingInfoCenter.default().nowPlayingInfo = info
}
}
}
}
}

View File

@ -58,7 +58,7 @@ class MP_WebWork:NSObject {
self.jsPath = self.jsPath + matchedString
print("Current base.JavaScript path:\(self.jsPath)")
//
MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
// MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
//
if let baseUrl = URL(string: self.jsPath) {
let request = URLRequest(url: baseUrl)
@ -177,7 +177,7 @@ extension MP_WebWork: WKNavigationDelegate, WKUIDelegate {
}else {
print("注入代码完成")
//
NotificationCenter.notificationKey.post(notificationName: .js_edit_completion)
// NotificationCenter.notificationKey.post(notificationName: .js_edit_completion)
}
}
}

View File

@ -1,6 +1,38 @@
import Foundation
///IP
struct JsonIPInfo: Codable {
let data:Datas?
let message:String?
let status:String?
enum CodingKeys: String, CodingKey {
case data = "data"
case message = "message"
case status = "status"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
data = try values.decodeIfPresent(Datas.self, forKey: .data)
message = try values.decodeIfPresent(String.self, forKey: .message)
status = try values.decodeIfPresent(String.self, forKey: .status)
}
struct Datas: Codable {
let isoCode:String?
let ip:String?
enum CodingKeys: String, CodingKey {
case isoCode = "isoCode"
case ip = "ip"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
isoCode = try values.decodeIfPresent(String.self, forKey: .isoCode)
ip = try values.decodeIfPresent(String.self, forKey: .ip)
}
}
}
///
struct JsonBrowses: Codable {
///访

View File

@ -18,7 +18,7 @@ class MPPositive_DownloadItemModel: NSManagedObject, MP_CoreDataManageableDelega
///
@NSManaged var title:String?
/////
@NSManaged var longBylineText:String?
@NSManaged var longBylineText:String?
///()
@NSManaged var lengthText:String?
///

View File

@ -32,6 +32,10 @@ class MPPositive_SongViewModel: NSObject {
var isCollection:Bool?
///
var isDlownd:Bool?
///KVO
var isKVO:Bool = false
///
var isPreload:Bool = false
///
var song:MPPositive_SongItemModel!
init(_ song:MPPositive_SongItemModel) {
@ -43,12 +47,20 @@ class MPPositive_SongViewModel: NSObject {
resourcePlayerItem = nil
resourcePlayerAsset = nil
resourcePlayerURL = nil
isKVO = false
}
//
func configure() {
reloadCollectionAndDownLoad()
index = song.index
//
if song.title != nil {
title = song.title!
}
//(/)
if song.shortBylineText != nil {
subtitle = song.shortBylineText!
}
if let first = song.resourceUrls?.first {
resourcePlayerURL = .init(string:first)
resourcePlayerAsset = .init(url: resourcePlayerURL)
@ -60,14 +72,6 @@ class MPPositive_SongViewModel: NSObject {
if song.reviewUrls?.first != nil {
coverUrl = .init(string: song.reviewUrls!.last!)
}
//
if song.title != nil {
title = song.title!
}
//(/)
if song.shortBylineText != nil {
subtitle = song.shortBylineText!
}
//
if song.lyrics != nil {
lyrics = song.lyrics
@ -95,6 +99,10 @@ class MPPositive_SongViewModel: NSObject {
}
//
func preloadAsset(_ asset:AVURLAsset) {
guard isPreload == false else {
return
}
print("\(title ?? "")开始预加载")
//
if #available(iOS 16, *) {
//ios16
@ -103,11 +111,13 @@ class MPPositive_SongViewModel: NSObject {
let playable = try await asset.load(.isPlayable)
if playable == true {
print("\(self.title ?? "")预加载成功")
isPreload = true
}else {
//
switch asset.status(of: .isPlayable) {
case .failed(let erro):
print("\(title ?? "")预加载失败,失败原因:\(erro.localizedDescription)")
preloadAsset(asset)
default:
break
}
@ -130,9 +140,11 @@ class MPPositive_SongViewModel: NSObject {
// key
DispatchQueue.main.async {
print("\(self.title ?? "")预加载成功")
self.isPreload = true
}
case .failed:
print("\(title ?? "")预加载失败,失败原因:\(error?.localizedDescription ?? "")")
preloadAsset(asset)
case .cancelled:
print("\(title ?? "")预加载被取消了")
default:

View File

@ -23,6 +23,10 @@ class MPPositive_BrowseLoadViewModel: NSObject {
browseModuleLists = browseModuleLists.filter{($0.items.count != 0)}
//UI
NotificationCenter.notificationKey.post(notificationName: .positive_browses_reload)
if isCompleted == true {
//
MP_AnalyticsManager.shared.home_b_module_showsucces_actionAction()
}
}
}
///

View File

@ -15,13 +15,18 @@ class MPPositive_PlayerLoadViewModel: NSObject {
///ViewModel
var currentVideo:MPPositive_SongViewModel!{
willSet{
if newValue != nil {
if currentVideo != nil {
//UI
NotificationCenter.notificationKey.post(notificationName: .positive_player_reload, object: currentVideo)
}else {
//UI
NotificationCenter.notificationKey.post(notificationName: .positive_player_reload)
DispatchQueue.main.asyncAfter(deadline: .now()) {
[weak self] in
guard let self = self else {return}
if newValue != nil {
MP_AnalyticsManager.shared.player_b_pvAction(newValue.song.videoId, videoname: newValue.title ?? "", artistname: newValue.song.shortBylineText ?? "")
if currentVideo != nil {
//UI
NotificationCenter.notificationKey.post(notificationName: .positive_player_reload, object: currentVideo)
}else {
//UI
NotificationCenter.notificationKey.post(notificationName: .positive_player_reload)
}
}
}
}
@ -43,7 +48,7 @@ class MPPositive_PlayerLoadViewModel: NSObject {
self.listViewVideos = []
}
///Video13VideoViewModel,
///Video14VideoViewModel,
func improveData(_ targetVideoId:String, isRandom:Bool = false) {
//Video
var array:[MPPositive_SongItemModel] = []
@ -59,9 +64,13 @@ class MPPositive_PlayerLoadViewModel: NSObject {
array.append(self.randomVideos[previousIndex])
}
let nextIndex = targetIndex+1
let lastIndex = targetIndex+2
if nextIndex < randomVideos.count {
array.append(self.randomVideos[nextIndex])
}
if lastIndex < randomVideos.count {
array.append(self.randomVideos[lastIndex])
}
}else {
//targetVideoId
guard let targetIndex = self.songVideos.firstIndex(where: {$0.videoId == targetVideoId}) else {
@ -74,9 +83,13 @@ class MPPositive_PlayerLoadViewModel: NSObject {
array.append(self.songVideos[previousIndex])
}
let nextIndex = targetIndex+1
let lastIndex = targetIndex+2
if nextIndex < songVideos.count {
array.append(self.songVideos[nextIndex])
}
if lastIndex < songVideos.count {
array.append(self.songVideos[lastIndex])
}
}
//ViewModelvideo
let videoIDs = Set(listViewVideos.map({$0.song.videoId}))
@ -123,8 +136,8 @@ class MPPositive_PlayerLoadViewModel: NSObject {
[weak self] in
//
self?.currentVideo = self?.listViewVideos.first(where: {$0.song.videoId == targetVideoId})
//
self?.listViewVideos = self?.listViewVideos.suffix(3)
//
self?.listViewVideos = self?.listViewVideos.suffix(4)
self?.group = nil
})
}
@ -180,19 +193,4 @@ class MPPositive_PlayerLoadViewModel: NSObject {
private func findVideoIdForDocument(_ videoId:String) -> Bool {
return MPPositive_DownloadItemModel.fetch(.init(format: "videoId == %@", videoId)).count != 0
}
///nextIDID
private func improveDataforLycirsAndRelated(_ song:MPPositive_SongItemModel, completion:@escaping(((String?,String?)) -> Void)) {
//next
MP_NetWorkManager.shared.requestNextLyricsAndRelated(song){ result in
completion(result)
}
}
///player
private func improveDataforResouceAndCover(_ song:MPPositive_SongItemModel, completion:@escaping((([String],[Int],[String]), [String]?) -> Void)) {
//player
MP_NetWorkManager.shared.requestAndroidPlayer(song.videoId, playlistId: "") { resourceUrls, coverUrls in
completion(resourceUrls,coverUrls)
}
}
}

View File

@ -29,6 +29,7 @@ class MPPositive_SearchResultsLoadViewModel: NSObject {
}
//
private func getSearchResults(_ text:String) {
MP_HUD.loading()
//
let tag = MPPositive_SearchTagModel.create()
tag.date = Date().timeZone()

View File

@ -17,12 +17,30 @@ class MPPositive_BaseViewController: MP_BaseViewController {
btn.addTarget(self, action: #selector(popActionClick(_ :)), for: .touchUpInside)
return btn
}()
//View
private lazy var errorView:UIView = createErrorView()
///
var errorBlock:(() -> Void)?
///
var retryBlock:(() -> Void)?
//View
lazy var navView:UIView = setTitleView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(navView)
}
deinit {
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.notificationKey.add(observer: self, selector: #selector(errorAction(_ :)), notificationName: .netWork_error_deal)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
//titleView
private func setTitleView() -> UIView {
let topView = UIView(frame: .init(x: 0, y: statusBarHeight, width: screen_Width, height: 50*width))
@ -55,4 +73,78 @@ class MPPositive_BaseViewController: MP_BaseViewController {
@objc private func popActionClick(_ sender:UIButton) {
navigationController?.popViewController(animated: true)
}
//errorView
private func createErrorView() -> UIView{
let errorView = UIView()
errorView.layer.masksToBounds = true
errorView.layer.borderColor = UIColor(hex: "#80F988").cgColor
errorView.layer.borderWidth = 1*width
errorView.layer.cornerRadius = 18*width
errorView.backgroundColor = .clear
//使
//
let iconImageView = UIImageView(image: .init(named: "empty"))
errorView.addSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.centerY.equalToSuperview().multipliedBy(0.65)
make.width.equalTo(211*width)
make.height.equalTo(172*width)
}
let label = createLabel("Failure to obtain data", font: .systemFont(ofSize: 13*width, weight: .regular), textColor: .white, textAlignment: .center)
errorView.addSubview(label)
label.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(iconImageView.snp.bottom).offset(8*width)
}
let retryBtn:UIButton = .init()
retryBtn.setTitle("Retry", for: .normal)
retryBtn.setTitleColor(.white, for: .normal)
retryBtn.titleLabel?.font = .systemFont(ofSize: 15*width, weight: .medium)
retryBtn.backgroundColor = .init(hex: "#80F988")
retryBtn.layer.masksToBounds = true
retryBtn.layer.cornerRadius = 16*width
retryBtn.addTarget(self, action: #selector(retryClick(_ :)), for: .touchUpInside)
errorView.addSubview(retryBtn)
retryBtn.snp.makeConstraints { make in
make.width.equalTo(180*width)
make.height.equalTo(45*width)
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(-15*width)
}
return errorView
}
//View
func setErrorView() {
view.addSubview(errorView)
errorView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(300*width)
make.height.equalTo(280*width)
}
}
//View
func removeErrorView() {
if errorView.superview != nil {
errorView.removeFromSuperview()
}
}
//
@objc private func errorAction(_ sender:Notification){
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
MP_HUD.text("Failure to obtain data", delay: 1.0){ [weak self] in
//
if self?.errorBlock != nil {
self?.errorBlock!()
}
}
}
}
@objc private func retryClick(_ sender:UIButton) {
if retryBlock != nil {
retryBlock!()
}
}
}

View File

@ -46,15 +46,14 @@ class MPPositive_MoreSongOperationsViewController: UIViewController {
let tableView = UITableView(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height), style: .plain)
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.contentInset = .init(top: 0, left: 0, bottom: bottomPadding, right: 0)
tableView.estimatedRowHeight = 200
tableView.rowHeight = UITableView.automaticDimension
tableView.dataSource = self
tableView.delegate = self
tableView.register(MPPositive_MoreOperationDownLoadTableViewCell.self, forCellReuseIdentifier: MPPositive_MoreOperationTableViewCellID)
tableView.register(MPPositive_MoreOperationDownLoadTableViewCell.self, forCellReuseIdentifier: MPPositive_MoreOperationDownLoadTableViewCellID)
return tableView
}()
private let MPPositive_MoreOperationTableViewCellID = "MPPositive_MoreOperationTableViewCell"
private let MPPositive_MoreOperationDownLoadTableViewCellID = "MPPositive_MoreOperationDownLoadTableViewCell"
private var song:MPPositive_SongItemModel!{
didSet{
iconImageView.kf.setImage(with: URL(string: song.reviewUrls?.last ?? ""), placeholder: placeholderImage)
@ -74,9 +73,13 @@ class MPPositive_MoreSongOperationsViewController: UIViewController {
}
}
}
var removeBlock:(() -> Void)?
init(_ song:MPPositive_SongItemModel) {
super.init(nibName: nil, bundle: nil)
self.song = song
DispatchQueue.main.async {
[weak self] in
self?.song = song
}
}
required init?(coder: NSCoder) {
@ -87,7 +90,7 @@ class MPPositive_MoreSongOperationsViewController: UIViewController {
super.viewDidLoad()
view.backgroundColor = .init(hex: "#282A2C")
view.layer.masksToBounds = true
view.layer.maskedCorners = [.layerMinXMinYCorner,.layerMinXMaxYCorner]
view.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
view.layer.cornerRadius = 18*width
configure()
}
@ -146,6 +149,7 @@ class MPPositive_MoreSongOperationsViewController: UIViewController {
}
}
MPPositive_LoadCoreModel.shared.reloadCollectionSongViewModel(nil)
MP_AnalyticsManager.shared.player_b_unlove_clickAction(song.videoId, videoname: song.title ?? "", artistname: song.shortBylineText ?? "")
}else{
self.collectionBtn.isSelected = true
let item = MPPositive_CollectionSongModel.create()
@ -157,6 +161,7 @@ class MPPositive_MoreSongOperationsViewController: UIViewController {
item.relatedID = song.relatedID
MPPositive_CollectionSongModel.save()
MPPositive_LoadCoreModel.shared.reloadCollectionSongViewModel(nil)
MP_AnalyticsManager.shared.player_b_love_clickAction(song.videoId, videoname: song.title ?? "", artistname: song.shortBylineText ?? "")
}
}
}
@ -166,8 +171,14 @@ extension MPPositive_MoreSongOperationsViewController:UITableViewDataSource, UIT
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_MoreOperationTableViewCellID, for: indexPath) as! MPPositive_MoreOperationDownLoadTableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_MoreOperationDownLoadTableViewCellID, for: indexPath) as! MPPositive_MoreOperationDownLoadTableViewCell
cell.title = isLoaded ? "Remove Download":"Add Download"
cell.restoreDownloadProgress(song)
cell.progressBlock = {
[weak self] in
guard let self = self else {return}
tableView.reloadRows(at: [.init(item: 0, section: 0)], with: .none)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -180,16 +191,95 @@ extension MPPositive_MoreSongOperationsViewController:UITableViewDataSource, UIT
}
}
MPPositive_LoadCoreModel.shared.reloadLoadSongViewModel(nil)
DownloadManager.shared.deleteFileDocuments(song.videoId) { [weak self] videoId in
MP_DownloadManager.shared.deleteFileDocuments(song.videoId) { [weak self] videoId in
guard let self = self else {return}
MP_HUD.progress("Loading...", delay: 0.5) {
self.isLoaded = false
MP_HUD.text("Removed", delay: 1.0, completion: nil)
if self.removeBlock != nil {
self.removeBlock!()
}
}
}
}else {
//
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_MoreOperationDownLoadTableViewCellID, for: indexPath) as! MPPositive_MoreOperationDownLoadTableViewCell
//
switch cell.loadBtn.state {
case .startDownload://
MP_AnalyticsManager.shared.player_b_download_clickAction(song.videoId ?? "", videoname: song.title ?? "", artistname: song.shortBylineText ?? "")
//
cell.loadBtn.state = .pending
tableView.reloadData()
//
guard let videoURL = URL(string: song.resourceUrls?.first ?? "") else {
MP_HUD.text("An error occurred while downloading. Please download again.", delay: 1.0) {[weak self] in
cell.loadBtn.state = .startDownload
self?.isLoaded = false
}
return
}
//
MP_DownloadManager.shared.downloadVideo(from: videoURL, song: song) { [weak self] progress in
DispatchQueue.main.async {
guard let self = self else {
//
cell.loadBtn.state = (MPPositive_DownloadItemModel.fetch(.init(format: "videoId == %@", (self?.song.videoId ?? ""))).count != 0) ? .downloaded:.startDownload
tableView.reloadData()
return
}
cell.loadBtn.state = .downloading
cell.loadBtn.stopDownloadButton.progress = progress
tableView.reloadData()
}
} completion: { [weak self] result in
//
switch result {
case .success(let song):
//
let item = MPPositive_DownloadItemModel.create()
item.coverImage = song.coverUrls!.last
item.reviewImage = song.reviewUrls!.last
item.title = song.title
item.longBylineText = song.longBylineText
item.lengthText = song.lengthText
item.shortBylineText = song.shortBylineText
item.lyrics = song.lyrics
item.lyricsID = song.lyricsID
item.videoId = song.videoId
item.relatedID = song.relatedID
MPPositive_DownloadItemModel.save()
DispatchQueue.main.async {
//线
if song.videoId == self?.song.videoId {
//
self?.isLoaded = true
}else {
//
self?.isLoaded = false
}
print("完成了对\(song.title ?? "")的下载")
//
cell.loadBtn.state = .downloaded
tableView.reloadData()
//
MPPositive_LoadCoreModel.shared.reloadLoadSongViewModel(nil)
MP_AnalyticsManager.shared.player_b_downloadsuccess_actionAction(item.videoId, videoname: item.title ?? "", artistname: item.shortBylineText ?? "")
}
case .failure(let error):
//
print("下载报错,错误详情\(error)")
DispatchQueue.main.async {
//
cell.loadBtn.state = .startDownload
tableView.reloadData()
}
MP_HUD.text("An error occurred while downloading. Please download again.", delay: 1.5, completion: nil)
}
}
default:
break
}
}
}
}

View File

@ -66,6 +66,10 @@ class MPPositive_LibraryViewController: MPPositive_BaseViewController {
super.viewWillAppear(animated)
reload()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
MP_AnalyticsManager.shared.me_b_pvAction()
}
//
private func reload() {
MPPositive_LoadCoreModel.shared.reloadCollectionListViewModels {

View File

@ -7,7 +7,7 @@
import UIKit
class MPPositive_LoveSongsViewController: MPPositive_BaseViewController {
class MPPositive_LoveSongsViewController: MPPositive_BaseViewController, UIViewControllerTransitioningDelegate {
private lazy var numbersLabel:UILabel = createLabel(font: .systemFont(ofSize: 18*width, weight: .regular), textColor: .white, textAlignment: .left)
///tableView
private lazy var tableView:UITableView = {
@ -67,6 +67,39 @@ extension MPPositive_LoveSongsViewController: UITableViewDataSource, UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchResultShowTableViewCellID, for: indexPath) as! MPPositive_SearchResultShowTableViewCell
cell.songViewModel = MPPositive_LoadCoreModel.shared.songViewModels[indexPath.row]
cell.moreBlock = {
[weak self] in
guard let self = self else {return}
MPPositive_Debouncer.shared.call {
MP_NetWorkManager.shared.requestNextList("", videoId: MPPositive_LoadCoreModel.shared.songViewModels[indexPath.row].collectionSong.videoId ?? ""){ [weak self] listSongs in
guard let first = listSongs.first else {return}
let group = DispatchGroup()
group.enter()
improveDataforLycirsAndRelated(first) {[weak self] (result) in
first.lyricsID = result.0
first.relatedID = result.1
group.leave()
}
group.enter()
//
improveDataforResouceAndCover(first) {[weak self] resourceUrls, coverUrls in
first.resourceUrls = resourceUrls.0
first.itags = resourceUrls.1
first.mimeTypes = resourceUrls.2
first.coverUrls = coverUrls
group.leave()
}
group.notify(queue: .main, execute: {
[weak self] in
MPPositive_ModalType = .MoreOperations
let moreVC = MPPositive_MoreSongOperationsViewController(first)
moreVC.transitioningDelegate = self
moreVC.modalPresentationStyle = .custom
self?.present(moreVC, animated: true)
})
}
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -93,4 +126,7 @@ extension MPPositive_LoveSongsViewController: UITableViewDataSource, UITableView
NotificationCenter.notificationKey.post(notificationName: .pup_player_vc)
}
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return MPPositive_PresentationController(presentedViewController: presented, presenting: presenting)
}
}

View File

@ -60,13 +60,39 @@ class MPPositive_OfflineSongsViewController: MPPositive_BaseViewController {
}
}
//MARK: - tableView
extension MPPositive_OfflineSongsViewController: UITableViewDataSource, UITableViewDelegate {
extension MPPositive_OfflineSongsViewController: UITableViewDataSource, UITableViewDelegate, UIViewControllerTransitioningDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return MPPositive_LoadCoreModel.shared.loadViewModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchResultShowTableViewCellID, for: indexPath) as! MPPositive_SearchResultShowTableViewCell
cell.loadViewModel = MPPositive_LoadCoreModel.shared.loadViewModels[indexPath.row]
cell.moreBlock = {
[weak self] in
guard let self = self else {return}
MPPositive_Debouncer.shared.call {
let item = MPPositive_LoadCoreModel.shared.loadViewModels[indexPath.row]
let song = MPPositive_SongItemModel()
song.coverUrls = [item.loadItem.coverImage]
song.reviewUrls = [item.loadItem.reviewImage]
song.title = item.loadItem.title
song.longBylineText = item.loadItem.longBylineText
song.lengthText = item.loadItem.lengthText
song.shortBylineText = item.loadItem.shortBylineText
song.lyrics = item.loadItem.lyrics
song.lyricsID = item.loadItem.lyricsID
song.videoId = item.loadItem.videoId
song.relatedID = item.loadItem.relatedID
MPPositive_ModalType = .MoreOperations
let moreVC = MPPositive_MoreSongOperationsViewController(song)
moreVC.removeBlock = {
self.reload()
}
moreVC.transitioningDelegate = self
moreVC.modalPresentationStyle = .custom
self.present(moreVC, animated: true)
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -96,4 +122,7 @@ extension MPPositive_OfflineSongsViewController: UITableViewDataSource, UITableV
NotificationCenter.notificationKey.post(notificationName: .pup_player_vc)
}
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return MPPositive_PresentationController(presentedViewController: presented, presenting: presenting)
}
}

View File

@ -7,7 +7,7 @@
import UIKit
class MPPositive_ArtistShowViewController: MPPositive_BaseViewController {
class MPPositive_ArtistShowViewController: MPPositive_BaseViewController, UIViewControllerTransitioningDelegate {
///
private lazy var headView:MPPositive_ArtistShowHeaderView = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: 385*width))
///Label
@ -61,17 +61,24 @@ class MPPositive_ArtistShowViewController: MPPositive_BaseViewController {
}()
private var artist:MPPositive_ArtistViewModel!{
didSet{
if artist != nil {
pagingView.isHidden = false
//
headView.artistid = self.browseid
headView.artist = artist
//
dataSource.titles = artist.lists.compactMap({$0.title})
nameLabel.text = artist.header?.title
dataSource.reloadData(selectedIndex: 0)
segmentView.reloadData()
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
if artist != nil {
pagingView.isHidden = false
//
headView.artistid = self.browseid
headView.artist = artist
//
dataSource.titles = artist.lists.compactMap({$0.title})
nameLabel.text = artist.header?.title
dataSource.reloadData(selectedIndex: 0)
segmentView.reloadData()
configure()
removeErrorView()
MP_HUD.hideNow()
}
}
}
}
@ -83,13 +90,11 @@ class MPPositive_ArtistShowViewController: MPPositive_BaseViewController {
init(_ browseId:String) {
super.init(nibName: nil, bundle: nil)
browseid = browseId
MP_HUD.loading()
//
MP_NetWorkManager.shared.requestArtist(browseId) { [weak self] result in
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
artist = result
}
guard let self = self else {return}
artist = result
}
}
@ -100,7 +105,33 @@ class MPPositive_ArtistShowViewController: MPPositive_BaseViewController {
super.viewDidLoad()
setPopBtn()
setTitle("")
configure()
errorBlock = {
[weak self] in
guard let self = self else {
return
}
//navView
view.subviews.forEach { item in
if item != self.navView {
//
if item.superview != nil {
item.removeFromSuperview()
}
}
}
//View
setErrorView()
MP_HUD.hideNow()
}
retryBlock = {
[weak self] in
guard let self = self else {return}
MP_HUD.loading()
MP_NetWorkManager.shared.requestArtist(self.browseid) { [weak self] result in
guard let self = self else {return}
artist = result
}
}
}
private func configure() {
@ -174,7 +205,7 @@ extension MPPositive_ArtistShowViewController: JXPagingViewDelegate{
switch item.browseItem.itemType {
case .artist:
//
let artistVC = MPPositive_ArtistShowViewController(item.browseItem.browseId ?? "")
let artistVC = MPPositive_ArtistShowViewController(item.browseItem.artistId ?? "")
navigationController?.pushViewController(artistVC, animated: true)
case .list:
//
@ -199,6 +230,42 @@ extension MPPositive_ArtistShowViewController: JXPagingViewDelegate{
break
}
}
showView.moreBlock = {
[weak self] (itemView) in
guard let self = self else {return}
MPPositive_Debouncer.shared.call {
MP_NetWorkManager.shared.requestNextList("", videoId: itemView.browseItem.videoId ?? ""){ [weak self] listSongs in
guard let first = listSongs.first else {return}
let group = DispatchGroup()
group.enter()
improveDataforLycirsAndRelated(first) {[weak self] (result) in
first.lyricsID = result.0
first.relatedID = result.1
group.leave()
}
group.enter()
//
improveDataforResouceAndCover(first) {[weak self] resourceUrls, coverUrls in
first.resourceUrls = resourceUrls.0
first.itags = resourceUrls.1
first.mimeTypes = resourceUrls.2
first.coverUrls = coverUrls
group.leave()
}
group.notify(queue: .main, execute: {
[weak self] in
MPPositive_ModalType = .MoreOperations
let moreVC = MPPositive_MoreSongOperationsViewController(first)
moreVC.transitioningDelegate = self
moreVC.modalPresentationStyle = .custom
self?.present(moreVC, animated: true)
})
}
}
}
return showView
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return MPPositive_PresentationController(presentedViewController: presented, presenting: presenting)
}
}

View File

@ -38,13 +38,51 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController{
// private var loadViewModel:MPPositive_BrowseLoadViewModel!
override func viewDidLoad() {
super.viewDidLoad()
setTitle("Musicoo")
setTitle("Musiclax")
confirgue()
//
MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
NotificationCenter.notificationKey.add(observer: self, selector: #selector(reloadAction(_ :)), notificationName: .positive_browses_reload)
MP_HUD.loading()
errorBlock = {
[weak self] in
guard let self = self else {
return
}
//navView
view.subviews.forEach { item in
if item != self.navView {
//
if item.superview != nil {
item.removeFromSuperview()
}
}
}
//View
setErrorView()
MP_HUD.hideNow()
}
retryBlock = {
[weak self] in
guard let self = self else {return}
MP_HUD.loading()
DispatchQueue.main.async {
//
MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists()
}
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
MP_AnalyticsManager.shared.home_b_pvAction()
}
//MARK: - UI
//
private func confirgue() {
@ -71,13 +109,20 @@ class MPPositive_HomeViewController: MPPositive_BaseViewController{
@objc private func reloadAction(_ sender:Notification) {
DispatchQueue.main.async {
[weak self] in
self?.tableView.reloadData()
guard let self = self else {return}
if tableView.superview == nil {
confirgue()
}
removeErrorView()
MP_HUD.hideNow()
tableView.reloadData()
}
}
//MARK: -
//
@objc private func menuRightClick(_ sender:UIButton) {
let setVC = MPSideA_SettingViewController()
navigationController?.pushViewController(setVC, animated: true)
}
}
//MARK: - tableView
@ -105,6 +150,7 @@ extension MPPositive_HomeViewController: UITableViewDataSource, UITableViewDeleg
cell.requestNextBlock = {
[weak self] (item) in
guard let self = self else {return}
MP_AnalyticsManager.shared.home_b_module_clickAction(MPPositive_BrowseLoadViewModel.shared.browseModuleLists[indexPath.section].title)
switch item.browseItem.itemType {
case .single:
///

View File

@ -7,7 +7,7 @@
import UIKit
///b
class MPPositive_ListShowViewController: MPPositive_BaseViewController {
class MPPositive_ListShowViewController: MPPositive_BaseViewController, UIViewControllerTransitioningDelegate {
//
private lazy var coverImageView:UIImageView = {
let imageView:UIImageView = .init()
@ -31,8 +31,7 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
//
private lazy var playListBtn:UIButton = {
let btn = UIButton()
btn.setBackgroundImage(UIImage(named: "List_UnPlay'logo"), for: .normal)
btn.setBackgroundImage(UIImage(named: "List_Played'logo"), for: .selected)
btn.setBackgroundImage(UIImage(named: "List_ShufflePlay'logo"), for: .normal)
btn.isUserInteractionEnabled = false
return btn
}()
@ -46,14 +45,7 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
btn.addTarget(self, action: #selector(collectionSwitchClick(_ :)), for: .touchUpInside)
return btn
}()
//
private lazy var playTypeBtn:UIButton = {
let btn = UIButton()
btn.setBackgroundImage(UIImage(named: "List_NormolPlay'logo"), for: .normal)
btn.setBackgroundImage(UIImage(named: "List_ShufflePlay'logo"), for: .selected)
btn.addTarget(self, action: #selector(collectionStatuClick(_ :)), for: .touchUpInside)
return btn
}()
//tableView/
private lazy var tableView:UITableView = {
let tableView = UITableView(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height), style: .plain)
@ -75,8 +67,11 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
[weak self] in
guard let self = self else {return}
if listOrAlbum != nil {
configure()
reload()
tableView.reloadData()
removeErrorView()
MP_HUD.hideNow()
}
}
}
@ -95,12 +90,12 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
self.params = params
self.centerTtitle = title
self.subtitle = subtitle
MP_HUD.loading()
super.init(nibName: nil, bundle: nil)
//
MP_NetWorkManager.shared.requestAlbumOrListDatas(browseId, params: params) { [weak self] result in
guard let self = self else {return}
listOrAlbum = result
}
}
@ -111,7 +106,33 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
super.viewDidLoad()
setTitle("")
setPopBtn()
configure()
errorBlock = {
[weak self] in
guard let self = self else {
return
}
//navView
view.subviews.forEach { item in
if item != self.navView {
//
if item.superview != nil {
item.removeFromSuperview()
}
}
}
//View
setErrorView()
MP_HUD.hideNow()
}
retryBlock = {
[weak self] in
guard let self = self else {return}
MP_HUD.loading()
MP_NetWorkManager.shared.requestAlbumOrListDatas(self.browseid, params: self.params) { [weak self] result in
guard let self = self else {return}
listOrAlbum = result
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
@ -128,7 +149,7 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
listOrAlbum.header?.setUrltoImage(coverImageView)
titleLabel.text = listOrAlbum.header?.title
descriptionLabel.text = listOrAlbum.header?._description
playListNumberLabel.text = "Play all (\(listOrAlbum.items.count))"
playListNumberLabel.text = "Play (\(listOrAlbum.items.count))"
}
//MARK: - UI
//
@ -173,16 +194,10 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
make.height.equalTo(32*width)
make.bottom.equalTo(blurView.snp.bottom).offset(-29*width)
}
view.addSubview(playTypeBtn)
playTypeBtn.snp.makeConstraints { make in
make.width.height.equalTo(24*width)
make.right.equalToSuperview().offset(-18*width)
make.centerY.equalTo(playListView.snp.centerY)
}
view.addSubview(collectionListBtn)
collectionListBtn.snp.makeConstraints { make in
make.width.height.equalTo(24*width)
make.right.equalToSuperview().offset(-56*width)
make.right.equalToSuperview().offset(-18*width)
make.centerY.equalTo(playListView.snp.centerY)
}
view.addSubview(tableView)
@ -235,7 +250,20 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
//MARK: -
///
@objc private func playListActionClick(_ sender:UITapGestureRecognizer) {
MPPositive_Debouncer.shared.call {
[weak self] in
guard let self = self, let item = listOrAlbum.items.randomElement() else {return}
//next
MP_NetWorkManager.shared.requestNextList(item.browseItem.playListId ?? "", videoId: item.browseItem.videoId ?? ""){ [weak self] listSongs in
guard let self = self else {return}
//playerloadViewModel
let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: item.browseItem.videoId ?? "")
lodaViewModel.improveData(item.browseItem.videoId ?? "")
MP_PlayerManager.shared.loadPlayer = lodaViewModel
//
NotificationCenter.notificationKey.post(notificationName: .pup_player_vc)
}
}
}
//
@objc private func collectionSwitchClick(_ sender:UIButton) {
@ -262,10 +290,7 @@ class MPPositive_ListShowViewController: MPPositive_BaseViewController {
MPPositive_LoadCoreModel.shared.reloadCollectionListViewModels(nil)
}
}
//
@objc private func collectionStatuClick(_ sender:UIButton) {
}
}
//MARK: - tableView
extension MPPositive_ListShowViewController: UITableViewDataSource, UITableViewDelegate {
@ -275,6 +300,39 @@ extension MPPositive_ListShowViewController: UITableViewDataSource, UITableViewD
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_MusicItemShowTableViewCellID, for: indexPath) as! MPPositive_MusicItemShowTableViewCell
cell.itemView = listOrAlbum.items[indexPath.row]
cell.moreBlock = {
[weak self] in
guard let self = self else {return}
MPPositive_Debouncer.shared.call {
MP_NetWorkManager.shared.requestNextList("", videoId: self.listOrAlbum.items[indexPath.row].browseItem.videoId ?? ""){ [weak self] listSongs in
guard let first = listSongs.first else {return}
let group = DispatchGroup()
group.enter()
improveDataforLycirsAndRelated(first) {[weak self] (result) in
first.lyricsID = result.0
first.relatedID = result.1
group.leave()
}
group.enter()
//
improveDataforResouceAndCover(first) {[weak self] resourceUrls, coverUrls in
first.resourceUrls = resourceUrls.0
first.itags = resourceUrls.1
first.mimeTypes = resourceUrls.2
first.coverUrls = coverUrls
group.leave()
}
group.notify(queue: .main, execute: {
[weak self] in
MPPositive_ModalType = .MoreOperations
let moreVC = MPPositive_MoreSongOperationsViewController(first)
moreVC.transitioningDelegate = self
moreVC.modalPresentationStyle = .custom
self?.present(moreVC, animated: true)
})
}
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -293,4 +351,7 @@ extension MPPositive_ListShowViewController: UITableViewDataSource, UITableViewD
}
}
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return MPPositive_PresentationController(presentedViewController: presented, presenting: presenting)
}
}

View File

@ -95,4 +95,28 @@ extension MPPositive_MoreContentViewController: UICollectionViewDataSource, UICo
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
MP_AnalyticsManager.shared.home_b_module_clickAction(browseModuleList.title)
switch showType {
case .Single:
///
MPPositive_Debouncer.shared.call {
[weak self] in
guard let self = self else {return}
//next
MP_NetWorkManager.shared.requestNextList(browseModuleList.items[indexPath.row].browseItem.playListId ?? "", videoId: browseModuleList.items[indexPath.row].browseItem.videoId ?? ""){ [weak self] listSongs in
guard let self = self else {return}
//playerloadViewModel
let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: browseModuleList.items[indexPath.row].browseItem.videoId ?? "")
lodaViewModel.improveData(browseModuleList.items[indexPath.row].browseItem.videoId ?? "")
MP_PlayerManager.shared.loadPlayer = lodaViewModel
NotificationCenter.notificationKey.post(notificationName: .pup_player_vc)
}
}
default:
//
let listVC = MPPositive_ListShowViewController(browseModuleList.items[indexPath.row].browseItem.browseId ?? "", params: browseModuleList.items[indexPath.row].browseItem.params ?? "", title: browseModuleList.items[indexPath.row].title ?? "", subtitle: browseModuleList.items[indexPath.row].subtitle ?? "")
navigationController?.pushViewController(listVC, animated: true)
}
}
}

View File

@ -123,25 +123,29 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
//
MP_PlayerManager.shared.runActionBlock = { [weak self] (currentTime, duration) in
guard let self = self else { return }
//
coverView.durationLabel.text = setTimesToMinSeconds(currentTime)
//
let remain:TimeInterval = duration - currentTime
coverView.maxTimesLabel.text = setTimesToMinSeconds(remain)
//
let value = currentTime/duration
coverView.sliderView.value = Float(value)
DispatchQueue.main.async {
//
self.coverView.durationLabel.text = setTimesToMinSeconds(currentTime)
//
let remain:TimeInterval = duration - currentTime
self.coverView.maxTimesLabel.text = setTimesToMinSeconds(remain)
//
let value = currentTime/duration
self.coverView.sliderView.value = Float(value)
}
}
//
MP_PlayerManager.shared.cacheValueBlock = { [weak self] (value, duration) in
guard let self = self else { return }
if value < duration {
//
let float = value/duration
coverView.progressView.setProgress(Float(float), animated: false)
}else {
//
coverView.progressView.setProgress(1, animated: false)
DispatchQueue.main.async {
if value < duration {
//
let float = value/duration
self.coverView.progressView.setProgress(Float(float), animated: false)
}else {
//
self.coverView.progressView.setProgress(1, animated: false)
}
}
}
switch MP_PlayerManager.shared.getPlayState() {
@ -308,7 +312,8 @@ class MPPositive_PlayerViewController: MPPositive_BaseViewController, UIViewCont
lyricsView.titleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.title
lyricsView.subtitleLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.subtitle
lyricsView.lyricsLabel.text = MP_PlayerManager.shared.loadPlayer.currentVideo?.lyrics?.isEmpty == true ? "No Lyrics":MP_PlayerManager.shared.loadPlayer.currentVideo?.lyrics
coverView.loadBtn.isSelected = MP_PlayerManager.shared.loadPlayer.currentVideo?.isDlownd ?? false
coverView.downloadButton.state = (MP_PlayerManager.shared.loadPlayer.currentVideo?.isDlownd ?? false) ? .downloaded:.startDownload
coverView.downloadButton.isUserInteractionEnabled = !(MP_PlayerManager.shared.loadPlayer.currentVideo?.isDlownd ?? false)
coverView.collectionSongBtn.isSelected = MP_PlayerManager.shared.loadPlayer.currentVideo?.isCollection ?? false
coverView.restoreDownloadProgress()
}

View File

@ -7,28 +7,35 @@
import UIKit
///
class MPPositive_RecommendViewController: MPPositive_BaseViewController {
class MPPositive_RecommendViewController: MPPositive_BaseViewController,UIViewControllerTransitioningDelegate {
//load
private var loadRecommend:MPPositive_RecommendLoadViewModel!{
didSet{
if loadRecommend != nil {
membersView.isHidden = false
segmentView.isHidden = false
listContainerView.isHidden = false
loadRecommend.resultReloadBlock = {
[weak self] in
//
guard let self = self else {return}
sectionLabel.text = loadRecommend.members.title
collectionView.reloadData()
dataSource.titles = loadRecommend?.sectionLists.compactMap({$0.title}) ?? []
dataSource.reloadData(selectedIndex: 0)
segmentView.reloadData()
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
if loadRecommend != nil {
membersView.isHidden = false
segmentView.isHidden = false
listContainerView.isHidden = false
loadRecommend.resultReloadBlock = {
[weak self] in
//
guard let self = self else {return}
sectionLabel.text = loadRecommend.members.title
collectionView.reloadData()
dataSource.titles = loadRecommend?.sectionLists.compactMap({$0.title}) ?? []
dataSource.reloadData(selectedIndex: 0)
segmentView.reloadData()
}
configure()
removeErrorView()
MP_HUD.hideNow()
}else {
membersView.isHidden = true
segmentView.isHidden = true
listContainerView.isHidden = true
}
}else {
membersView.isHidden = true
segmentView.isHidden = true
listContainerView.isHidden = true
}
}
}
@ -93,10 +100,13 @@ class MPPositive_RecommendViewController: MPPositive_BaseViewController {
listContainerView.backgroundColor = .clear
return listContainerView
}()
private var browseId:String!
///
/// - Parameter browseId: id
init(_ browseId:String) {
super.init(nibName: nil, bundle: nil)
self.browseId = browseId
MP_HUD.loading()
DispatchQueue.main.async {
[weak self] in
self?.loadRecommend = .init(browseId)
@ -111,7 +121,32 @@ class MPPositive_RecommendViewController: MPPositive_BaseViewController {
super.viewDidLoad()
setTitle("Recommend")
setPopBtn()
configure()
errorBlock = {
[weak self] in
guard let self = self else {
return
}
//navView
view.subviews.forEach { item in
if item != self.navView {
//
if item.superview != nil {
item.removeFromSuperview()
}
}
}
//View
setErrorView()
MP_HUD.hideNow()
}
retryBlock = {
[weak self] in
guard let self = self else {return}
MP_HUD.loading()
DispatchQueue.main.async {
self.loadRecommend = .init(self.browseId)
}
}
}
private func configure() {
view.addSubview(membersView)
@ -208,6 +243,42 @@ extension MPPositive_RecommendViewController: JXSegmentedListContainerViewDataSo
break
}
}
showView.moreBlock = {
[weak self] (itemView) in
guard let self = self else {return}
MPPositive_Debouncer.shared.call {
MP_NetWorkManager.shared.requestNextList("", videoId: itemView.browseItem.videoId ?? ""){ [weak self] listSongs in
guard let first = listSongs.first else {return}
let group = DispatchGroup()
group.enter()
improveDataforLycirsAndRelated(first) {[weak self] (result) in
first.lyricsID = result.0
first.relatedID = result.1
group.leave()
}
group.enter()
//
improveDataforResouceAndCover(first) {[weak self] resourceUrls, coverUrls in
first.resourceUrls = resourceUrls.0
first.itags = resourceUrls.1
first.mimeTypes = resourceUrls.2
first.coverUrls = coverUrls
group.leave()
}
group.notify(queue: .main, execute: {
[weak self] in
MPPositive_ModalType = .MoreOperations
let moreVC = MPPositive_MoreSongOperationsViewController(first)
moreVC.transitioningDelegate = self
moreVC.modalPresentationStyle = .custom
self?.present(moreVC, animated: true)
})
}
}
}
return showView
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return MPPositive_PresentationController(presentedViewController: presented, presenting: presenting)
}
}

View File

@ -7,7 +7,7 @@
import UIKit
///
class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController {
class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController, UIViewControllerTransitioningDelegate {
//MARK: - View
//textField
private lazy var searchTextField:UITextField = {
@ -36,6 +36,7 @@ class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController {
btn.addTarget(self, action: #selector(backPopClick(_ :)), for: .touchUpInside)
return btn
}()
//
private var debounceTimer: Timer?
//
@ -48,6 +49,7 @@ class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController {
//
suggestionView.isHidden = false
suggestionView.suggestions = suggestionList.attributedTexts
MP_AnalyticsManager.shared.search_sug_showAction()
}else {
suggestionView.isHidden = true
suggestionView.suggestions = nil
@ -70,6 +72,7 @@ class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController {
searchTextField.text = text
searchText = text
resultsShowView.loadModel = .init(text)
MP_AnalyticsManager.shared.search_sug_clickAction(text)
}
required init?(coder: NSCoder) {
@ -87,6 +90,7 @@ class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController {
searchText = text
searchTextField.text = text
resultsShowView.loadModel = .init(text)
MP_AnalyticsManager.shared.search_sug_clickAction(text)
suggestionView.isHidden = true
}
resultsShowView.scrollBlock = {
@ -124,10 +128,52 @@ class MPPositive_SearchResultShowViewController: MPPositive_BaseViewController {
break
}
}
resultsShowView.moreBlock = {
[weak self] (itemView) in
guard let self = self else {return}
MPPositive_Debouncer.shared.call {
MP_NetWorkManager.shared.requestNextList("", videoId: itemView.item.videoId ?? ""){ [weak self] listSongs in
guard let first = listSongs.first else {return}
let group = DispatchGroup()
group.enter()
improveDataforLycirsAndRelated(first) {[weak self] (result) in
first.lyricsID = result.0
first.relatedID = result.1
group.leave()
}
group.enter()
//
improveDataforResouceAndCover(first) {[weak self] resourceUrls, coverUrls in
first.resourceUrls = resourceUrls.0
first.itags = resourceUrls.1
first.mimeTypes = resourceUrls.2
first.coverUrls = coverUrls
group.leave()
}
group.notify(queue: .main, execute: {
[weak self] in
MPPositive_ModalType = .MoreOperations
let moreVC = MPPositive_MoreSongOperationsViewController(first)
moreVC.transitioningDelegate = self
moreVC.modalPresentationStyle = .custom
self?.present(moreVC, animated: true)
})
}
}
}
errorBlock = {
[weak self] in
MP_HUD.hideNow()
self?.resultsShowView.isHidden = false
self?.resultsShowView.emptyImageView.isHidden = false
}
}
deinit{
debounceTimer = nil
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return MPPositive_PresentationController(presentedViewController: presented, presenting: presenting)
}
private func configure() {
//
let searchView = createSearchView()
@ -246,6 +292,7 @@ extension MPPositive_SearchResultShowViewController:UITextFieldDelegate {
self.searchText = text
//
resultsShowView.loadModel = .init(text)
MP_AnalyticsManager.shared.search_sug_clickAction(text)
suggestionView.isHidden = true
//
view.endEditing(true)

View File

@ -48,6 +48,7 @@ class MPPositive_SearchViewController: MPPositive_BaseViewController {
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
MP_AnalyticsManager.shared.search_pvAction()
}
//
private func configure() {

View File

@ -8,30 +8,41 @@
import UIKit
import DownloadButton
class MPPositive_MoreOperationDownLoadTableViewCell: UITableViewCell {
//
private lazy var iconImageView:UIImageView = {
let imageView:UIImageView = .init()
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true
return imageView
}()
//
private lazy var LoadBtn:PKDownloadButton = {
lazy var loadBtn:PKDownloadButton = {
let btn:PKDownloadButton = .init()
//
btn.isUserInteractionEnabled = false
btn.downloadedButton.cleanDefaultAppearance()
// btn.downloadedButton.setBackgroundImage(UIImage(named: ""), for: <#T##UIControl.State#>)
//
btn.startDownloadButton.cleanDefaultAppearance()
btn.startDownloadButton.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal)
//
btn.downloadedButton.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .normal)
btn.downloadedButton.isUserInteractionEnabled = false
//
btn.stopDownloadButton.stopButton.setImage(UIImage(named: "download"), for: .normal)
btn.stopDownloadButton.isUserInteractionEnabled = false
btn.stopDownloadButton.tintColor = UIColor(hex: "#80F988")
btn.stopDownloadButton.stopButtonWidth = 1
btn.stopDownloadButton.stopButton.backgroundColor = .clear
btn.stopDownloadButton.stopButton.tintColor = .clear
btn.stopDownloadButton.filledLineWidth = 3*width
btn.stopDownloadButton.filledLineStyleOuter = true
//
btn.pendingView.tintColor = UIColor(hex: "#80F988")
btn.pendingView.radius = 12*width
btn.pendingView.emptyLineRadians = 2*width
btn.pendingView.spinTime = 3
return btn
}()
private lazy var titleLabel:UILabel = createLabel(font: .systemFont(ofSize: 14*width, weight: .regular), textColor: .white, textAlignment: .left)
var title:String!{
didSet{
iconImageView.image = UIImage(named: title)
titleLabel.text = title
}
}
var progressBlock:(() -> Void)?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
@ -53,8 +64,8 @@ class MPPositive_MoreOperationDownLoadTableViewCell: UITableViewCell {
// Configure the view for the selected state
}
private func configure() {
contentView.addSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
contentView.addSubview(loadBtn)
loadBtn.snp.makeConstraints { make in
make.width.height.equalTo(24*width)
make.top.equalToSuperview().offset(12*width).priority(999)
make.bottom.equalToSuperview().offset(-12*width)
@ -63,7 +74,36 @@ class MPPositive_MoreOperationDownLoadTableViewCell: UITableViewCell {
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalTo(iconImageView.snp.right).offset(12*width)
make.left.equalTo(loadBtn.snp.right).offset(12*width)
}
}
//
public func restoreDownloadProgress(_ song:MPPositive_SongItemModel?) {
guard let song = song else {
return
}
//video
if let progress = MP_DownloadManager.shared.getProgress(for: song.videoId) {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//URL
loadBtn.state = .downloading
//
loadBtn.stopDownloadButton.progress = progress
if progressBlock != nil {
progressBlock!()
}
}
}else {
//,
if MPPositive_DownloadItemModel.fetch(.init(format: "videoId == %@", song.videoId)).count != 0 {
//
loadBtn.state = .downloaded
}else {
//
loadBtn.state = .startDownload
}
}
}
}

View File

@ -24,14 +24,14 @@ class MPPositive_ArtistShowSongTableViewCell: UITableViewCell {
btn.addTarget(self, action: #selector(moreActionClick(_ :)), for: .touchUpInside)
return btn
}()
///
private lazy var loadBtn:UIButton = {
let btn:UIButton = .init()
btn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal)
btn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected)
btn.addTarget(self, action: #selector(loadActionClick(_ :)), for: .touchUpInside)
return btn
}()
// ///
// private lazy var loadBtn:UIButton = {
// let btn:UIButton = .init()
// btn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal)
// btn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected)
// btn.addTarget(self, action: #selector(loadActionClick(_ :)), for: .touchUpInside)
// return btn
// }()
var itemView:MPPositive_BrowseItemViewModel!{
didSet{
itemView.setUrltoImage(iconImageView)
@ -74,17 +74,17 @@ class MPPositive_ArtistShowSongTableViewCell: UITableViewCell {
make.centerY.equalTo(iconImageView.snp.centerY)
make.right.equalToSuperview().offset(-18*width)
}
contentView.addSubview(loadBtn)
loadBtn.snp.makeConstraints { make in
make.width.height.equalTo(24*width)
make.centerY.equalTo(iconImageView.snp.centerY)
make.right.equalToSuperview().offset(-54*width)
}
// contentView.addSubview(loadBtn)
// loadBtn.snp.makeConstraints { make in
// make.width.height.equalTo(24*width)
// make.centerY.equalTo(iconImageView.snp.centerY)
// make.right.equalToSuperview().offset(-54*width)
// }
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalTo(iconImageView.snp.top).offset(10*width)
make.left.equalTo(iconImageView.snp.right).offset(12*width)
make.right.equalTo(loadBtn.snp.left).offset(-10*width)
make.right.equalTo(moreBtn.snp.left).offset(-10*width)
}
contentView.addSubview(subtitleLabel)
subtitleLabel.snp.makeConstraints { make in
@ -93,10 +93,13 @@ class MPPositive_ArtistShowSongTableViewCell: UITableViewCell {
make.right.equalTo(titleLabel.snp.right)
}
}
var moreBlock:(() -> Void)?
//
@objc private func moreActionClick(_ sender:UIButton) {
guard moreBlock != nil else {
return
}
moreBlock!()
}
//
@objc private func loadActionClick(_ sender:UIButton) {

View File

@ -66,6 +66,7 @@ class MPPositive_ArtistShowTypeView: UIView, JXPagingViewListViewDelegate {
fileprivate var listViewDidScrollCallback: ((UIScrollView) -> ())?
//
var chooseItemBlock:((MPPositive_BrowseItemViewModel) -> Void)?
var moreBlock:((MPPositive_BrowseItemViewModel) -> Void)?
init(frame: CGRect, list:MPPositive_ArtistContentListViewModel) {
super.init(frame: frame)
backgroundColor = .init(hex: "#151718")
@ -133,6 +134,15 @@ extension MPPositive_ArtistShowTypeView:UITableViewDataSource, UITableViewDelega
case .single:
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_ArtistShowSongTableViewCellID) as! MPPositive_ArtistShowSongTableViewCell
cell.itemView = sectionList.itemViews[indexPath.row]
cell.moreBlock = {
[weak self] in
guard let self = self else {
return
}
if moreBlock != nil {
moreBlock!(sectionList.itemViews[indexPath.row])
}
}
return cell
case .list:
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_ArtistShowListableViewCellID, for: indexPath) as! MPPositive_ArtistShowListableViewCell

View File

@ -41,10 +41,10 @@ class MPPositive_MusicItemShowTableViewCell: UITableViewCell {
itemView.setUrltoImage(coverImageView)
titleLabel.text = itemView.title
subtitleLabel.text = itemView.subtitle
//
}
}
var moreBlock:(() -> Void)?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
@ -101,7 +101,10 @@ class MPPositive_MusicItemShowTableViewCell: UITableViewCell {
}
//
@objc private func moreActionClick(_ sender:UIButton) {
guard moreBlock != nil else {
return
}
moreBlock!()
}
//
@objc private func loadActionClick(_ sender:UIButton) {

View File

@ -5,9 +5,9 @@
import UIKit
import DownloadButton
//BView(View)
class MPPositive_PlayerCoverView: UIView {
class MPPositive_PlayerCoverView: UIView, PKDownloadButtonDelegate {
//View
private var loadView = CircularProgressView()
// private var loadView = CircularProgressView()
///
lazy var coverImageView:UIImageView = {
@ -30,13 +30,13 @@ class MPPositive_PlayerCoverView: UIView {
return btn
}()
///
lazy var loadBtn:UIButton = {
let btn:UIButton = .init()
btn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal)
btn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected)
btn.addTarget(self, action: #selector(loadActionClick(_ :)), for: .touchUpInside)
return btn
}()
// lazy var loadBtn:UIButton = {
// let btn:UIButton = .init()
// btn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal)
// btn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected)
// btn.addTarget(self, action: #selector(loadActionClick(_ :)), for: .touchUpInside)
// return btn
// }()
///
lazy var downloadButton:PKDownloadButton = {
let btn:PKDownloadButton = .init()
@ -50,10 +50,18 @@ class MPPositive_PlayerCoverView: UIView {
btn.stopDownloadButton.stopButton.setImage(UIImage(named: "download"), for: .normal)
btn.stopDownloadButton.isUserInteractionEnabled = false
btn.stopDownloadButton.tintColor = UIColor(hex: "#80F988")
btn.stopDownloadButton.filledLineWidth = 1.5
btn.stopDownloadButton.stopButtonWidth = 1
btn.stopDownloadButton.stopButton.backgroundColor = .clear
btn.stopDownloadButton.stopButton.tintColor = .clear
btn.stopDownloadButton.filledLineWidth = 3*width
btn.stopDownloadButton.filledLineStyleOuter = true
//
// btn.pendingView.tintColor = .
btn.pendingView.tintColor = UIColor(hex: "#80F988")
btn.pendingView.radius = 12*width
btn.pendingView.emptyLineRadians = 2*width
btn.pendingView.spinTime = 3
btn.delegate = self
return btn
}()
@ -104,55 +112,30 @@ class MPPositive_PlayerCoverView: UIView {
}
required init?(coder: NSCoder) {
super.init(coder: coder)
// NotificationCenter.default.addObserver(self, selector: #selector(updateProgress(_:)), name: Notification.Name("DownloadProgressUpdated"), object: nil)
//
// //
// restoreDownloadProgress()
}
//
public func restoreDownloadProgress() {
if let currentVideo = MP_PlayerManager.shared.loadPlayer.currentVideo,
let videoURLString = currentVideo.song.resourceUrls?.first,
let videoURL = URL(string: videoURLString) {
if let progress = DownloadManager.shared.getProgress(for: videoURL) {
//VideoID
addCircularProgressBar(over: loadBtn)
loadView.setProgress(to: progress)
self.layoutIfNeeded()
self.loadBtn.setBackgroundImage(UIImage(named: ""), for: .normal)
self.loadBtn.setImage(UIImage(named: "download"), for: .normal)
self.addCircularProgressBar(over: self.loadBtn)
self.loadView.setProgress(to: progress)
}else {
//videoID
if (loadView.superview) != nil {
loadView.removeFromSuperview()
}
self.loadBtn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal)
self.loadBtn.setImage(UIImage(), for: .normal)
self.loadBtn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .selected)
}
}
}
@objc private func updateProgress(_ notification: Notification) {
if let userInfo = notification.userInfo,
let url = userInfo["url"] as? URL,
let progress = userInfo["progress"] as? CGFloat,
let currentVideo = MP_PlayerManager.shared.loadPlayer.currentVideo,
let videoURLString = currentVideo.song.resourceUrls?.first,
let videoURL = URL(string: videoURLString),
videoURL == url {
loadView.setProgress(to: progress)
if loadView.progress.isEqual(to: 1.0){
self.loadView.removeFromSuperview()
self.loadBtn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .normal)
self.loadBtn.setImage(UIImage(named: ""), for: .normal)
}
}
}
guard let currentVideo = MP_PlayerManager.shared.loadPlayer?.currentVideo else {
return
}
//video
if let progress = MP_DownloadManager.shared.getProgress(for: currentVideo.song.videoId) {
DispatchQueue.main.async {
[weak self] in
guard let self = self else {return}
//URL
downloadButton.state = .downloading
downloadButton.isUserInteractionEnabled = false
//
downloadButton.stopDownloadButton.progress = progress
}
}else {
//
downloadButton.state = .startDownload
downloadButton.isUserInteractionEnabled = true
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@ -185,16 +168,16 @@ class MPPositive_PlayerCoverView: UIView {
make.top.equalTo(titleLabel.snp.bottom).offset(6*width)
}
//
addSubview(loadBtn)
loadBtn.snp.makeConstraints { make in
addSubview(downloadButton)
downloadButton.snp.makeConstraints { make in
make.right.equalTo(coverImageView.snp.right).offset(-12*width)
make.top.equalTo(coverImageView.snp.bottom).offset(47*width)
make.width.height.equalTo(24*width)
}
addSubview(collectionSongBtn)
collectionSongBtn.snp.makeConstraints { make in
make.right.equalTo(loadBtn.snp.left).offset(-20*width)
make.centerY.equalTo(loadBtn.snp.centerY)
make.right.equalTo(downloadButton.snp.left).offset(-20*width)
make.centerY.equalTo(downloadButton.snp.centerY)
make.width.height.equalTo(24*width)
}
addSubview(progressView)
@ -219,8 +202,6 @@ class MPPositive_PlayerCoverView: UIView {
make.right.equalTo(sliderView.snp.right)
make.top.equalTo(sliderView.snp.bottom).offset(5*width)
}
NotificationCenter.default.addObserver(self, selector: #selector(updateProgress(_:)), name: Notification.Name("DownloadProgressUpdated"), object: nil)
//
restoreDownloadProgress()
}
@ -272,6 +253,7 @@ class MPPositive_PlayerCoverView: UIView {
}
MP_PlayerManager.shared.loadPlayer.currentVideo.reloadCollectionAndDownLoad()
MPPositive_LoadCoreModel.shared.reloadCollectionSongViewModel(nil)
MP_AnalyticsManager.shared.player_b_unlove_clickAction(MP_PlayerManager.shared.loadPlayer.currentVideo?.song.videoId ?? "", videoname: MP_PlayerManager.shared.loadPlayer.currentVideo?.song.title ?? "", artistname: MP_PlayerManager.shared.loadPlayer.currentVideo?.song.shortBylineText ?? "")
}
}else{
self.collectionSongBtn.isSelected = true
@ -286,132 +268,94 @@ class MPPositive_PlayerCoverView: UIView {
MPPositive_CollectionSongModel.save()
MP_PlayerManager.shared.loadPlayer.currentVideo.reloadCollectionAndDownLoad()
MPPositive_LoadCoreModel.shared.reloadCollectionSongViewModel(nil)
MP_AnalyticsManager.shared.player_b_love_clickAction(MP_PlayerManager.shared.loadPlayer.currentVideo?.song.videoId ?? "", videoname: MP_PlayerManager.shared.loadPlayer.currentVideo?.song.title ?? "", artistname: MP_PlayerManager.shared.loadPlayer.currentVideo?.song.shortBylineText ?? "")
}
}
}
@objc private func loadActionClick(_ sender: UIButton) {
if MP_PlayerManager.shared.loadPlayer.currentVideo?.isDlownd == false {
addCircularProgressBar(over: sender)
self.loadBtn.setBackgroundImage(UIImage(named: ""), for: .normal)
self.loadBtn.setImage(UIImage(named: "download"), for: .normal)
if let currentVideo = MP_PlayerManager.shared.loadPlayer.currentVideo,
let videoURLString = currentVideo.song.resourceUrls?.first,
let videoURL = URL(string: videoURLString) {
DownloadManager.shared.downloadVideo(from: videoURL, song: MP_PlayerManager.shared.loadPlayer.currentVideo.song, progressHandler: { [weak self] progress in
DispatchQueue.main.async {
self?.loadView.setProgress(to: progress)
NotificationCenter.default.post(name: Notification.Name("DownloadProgressUpdated"), object: nil, userInfo: ["url": videoURL, "progress": progress])
//
func downloadButtonTapped(_ downloadButton: PKDownloadButton!, currentState state: PKDownloadButtonState) {
guard MP_PlayerManager.shared.loadPlayer?.currentVideo != nil, MP_PlayerManager.shared.loadPlayer?.currentVideo?.isDlownd == false else {
return
}
//
switch state {
case .startDownload: //
//
downloadButton.state = .pending
//
downloadButton.isUserInteractionEnabled = false
//
guard let currentVideo = MP_PlayerManager.shared.loadPlayer?.currentVideo, let videoURL = currentVideo.resourcePlayerURL else {
MP_HUD.text("An error occurred while downloading. Please download again.", delay: 1.0) {
downloadButton.state = .startDownload
downloadButton.isUserInteractionEnabled = true
}
return
}
MP_AnalyticsManager.shared.player_b_download_clickAction(currentVideo.song.videoId, videoname: currentVideo.song.title ?? "", artistname: currentVideo.song.shortBylineText ?? "")
//
MP_DownloadManager.shared.downloadVideo(from: videoURL, song: currentVideo.song) { [weak self] progress in
guard let self = self, currentVideo.song.videoId == MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId else {
//
downloadButton.state = (MP_PlayerManager.shared.loadPlayer.currentVideo?.isDlownd ?? false) ? .downloaded:.startDownload
downloadButton.isUserInteractionEnabled = !(MP_PlayerManager.shared.loadPlayer.currentVideo?.isDlownd ?? false)
return
}
downloadButton.state = .downloading
downloadButton.stopDownloadButton.progress = progress
} completion: { [weak self] result in
guard let self = self else {return}
//
switch result {
case .success(let song):
//
let item = MPPositive_DownloadItemModel.create()
item.coverImage = song.coverUrls!.last
item.reviewImage = song.reviewUrls!.last
item.title = song.title
item.longBylineText = song.longBylineText
item.lengthText = song.lengthText
item.shortBylineText = song.shortBylineText
item.lyrics = song.lyrics
item.lyricsID = song.lyricsID
item.videoId = song.videoId
item.relatedID = song.relatedID
MPPositive_DownloadItemModel.save()
DispatchQueue.main.async {
//线
if song.videoId == MP_PlayerManager.shared.loadPlayer?.currentVideo?.song.videoId {
//
MP_PlayerManager.shared.loadPlayer.currentVideo.reloadCollectionAndDownLoad()
}else {
//
}
}, completion: { [weak self] result in
switch result {
case .success(let song):
let item = MPPositive_DownloadItemModel.create()
//
item.coverImage = song.coverUrls!.last
item.reviewImage = song.reviewUrls!.last
item.title = song.title
item.longBylineText = song.longBylineText
item.lengthText = song.lengthText
item.shortBylineText = song.shortBylineText
item.lyrics = song.lyrics
item.lyricsID = song.lyricsID
item.videoId = song.videoId
item.relatedID = song.relatedID
MPPositive_DownloadItemModel.save()
DispatchQueue.main.async {
self?.loadView.removeFromSuperview()
MP_PlayerManager.shared.loadPlayer.currentVideo.reloadCollectionAndDownLoad()
self?.loadBtn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .normal)
self?.loadBtn.setImage(UIImage(named: ""), for: .normal)
print("完成了对\(song.title ?? "")的下载")
MPPositive_LoadCoreModel.shared.reloadLoadSongViewModel(nil)
}
case .failure(let error):
print("Download failed with error: \(error)")
DispatchQueue.main.async {
self?.loadView.removeFromSuperview()
self?.loadBtn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal)
self?.loadBtn.setImage(UIImage(named: ""), for: .normal)
}
MP_HUD.text("Download timed out, please download again", delay: 1.5, completion: nil)
}
})
print("完成了对\(song.title ?? "")的下载")
//
downloadButton.state = .downloaded
downloadButton.isUserInteractionEnabled = false
//
MPPositive_LoadCoreModel.shared.reloadLoadSongViewModel(nil)
MP_AnalyticsManager.shared.player_b_downloadsuccess_actionAction(item.videoId, videoname: item.title ?? "", artistname: item.shortBylineText ?? "")
}
case .failure(let error):
//
print("下载报错,错误详情\(error)")
DispatchQueue.main.async {
//
downloadButton.state = .startDownload
downloadButton.isUserInteractionEnabled = true
}
MP_HUD.text("An error occurred while downloading. Please download again.", delay: 1.5, completion: nil)
}
}
case .pending://
break
case .downloading:
break
case .downloaded:
break
@unknown default:
break
}
// //
// @objc private func loadActionClick(_ sender:UIButton) {
//
// if MP_PlayerManager.shared.loadPlayer.currentVideo?.isDlownd == false{
//
// //
// addCircularProgressBar(over: sender)
//
// self.loadBtn.setBackgroundImage(UIImage(named: ""), for: .normal)
// self.loadBtn.setImage(UIImage(named: "download"), for: .normal)
//
// //
// if MP_PlayerManager.shared.loadPlayer.currentVideo != nil {
//
// //
// if let currentVideo = MP_PlayerManager.shared.loadPlayer.currentVideo, let videoURLString = currentVideo.song.resourceUrls?.first, let videoURL = URL(string: videoURLString) {
// let videoId = currentVideo.song.videoId ?? "default_video_id"
// DownloadManager.shared.downloadVideo(from: videoURL, videoId: videoId, progressView: loadView, completion: { [weak self] result in
// switch result {
// case .success(let fileURL):
// let item = MPPositive_DownloadItemModel.create()
// item.resourcePath = "\(fileURL)"
// item.coverImage = URL(string: MP_PlayerManager.shared.loadPlayer.currentVideo.song.coverUrls!.first!)
// item.reviewImage = URL(string: MP_PlayerManager.shared.loadPlayer.currentVideo.song.reviewUrls!.first!)
// item.title = MP_PlayerManager.shared.loadPlayer.currentVideo.song.title
// item.longBylineText = MP_PlayerManager.shared.loadPlayer.currentVideo.song.longBylineText
// item.lengthText = MP_PlayerManager.shared.loadPlayer.currentVideo.song.lengthText
// item.shortBylineText = MP_PlayerManager.shared.loadPlayer.currentVideo.song.shortBylineText
// item.lyrics = MP_PlayerManager.shared.loadPlayer.currentVideo.lyrics
// item.videoId = MP_PlayerManager.shared.loadPlayer.currentVideo.song.videoId
// item.relatedID = MP_PlayerManager.shared.loadPlayer.currentVideo.song.relatedID
//
// MPPositive_DownloadItemModel.save()
// DispatchQueue.main.async {
// MP_PlayerManager.shared.loadPlayer.currentVideo.reloadCollectionAndDownLoad()
// self?.loadBtn.setBackgroundImage(UIImage(named: "Song_Loaded'logo"), for: .normal)
// self?.loadBtn.setImage(UIImage(named: ""), for: .normal)
// }
//
// self?.loadView.removeFromSuperview()
// case .failure(let error):
// print("Download failed with error: \(error)")
// self?.loadView.removeFromSuperview()
// DispatchQueue.main.async {
// self?.loadBtn.setBackgroundImage(UIImage(named: "Song_Unload'logo"), for: .normal)
// self?.loadBtn.setImage(UIImage(named: ""), for: .normal)
// }
// MP_HUD.text("Download timed out, please download again", delay: 1.5, completion: nil)
// }
// })
// }
// }
// }
//
// }
// //
private func addCircularProgressBar(over button: UIButton) {
loadView.removeFromSuperview() //
loadView = CircularProgressView(frame: button.bounds)
addSubview(loadView)
loadView.snp.makeConstraints { make in
make.center.equalTo(button)
make.width.height.equalTo(button)
}
}
}
}

View File

@ -35,7 +35,7 @@ class MPPositive_RecommendShowTypeView: UIView, JXSegmentedListContainerViewList
}
//
var chooseItemBlock:((MPPositive_BrowseItemViewModel) -> Void)?
var moreBlock:((MPPositive_BrowseItemViewModel) -> Void)?
init(_ frame:CGRect, list:MPPositive_RecommendListViewModel) {
super.init(frame: frame)
backgroundColor = .clear
@ -82,6 +82,15 @@ extension MPPositive_RecommendShowTypeView:UITableViewDataSource, UITableViewDel
case .single:
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_ArtistShowSongTableViewCellID) as! MPPositive_ArtistShowSongTableViewCell
cell.itemView = sectionList.items[indexPath.row]
cell.moreBlock = {
[weak self] in
guard let self = self else {
return
}
if moreBlock != nil {
moreBlock!(sectionList.items[indexPath.row])
}
}
return cell
case .list:
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_ArtistShowListableViewCellID, for: indexPath) as! MPPositive_ArtistShowListableViewCell

View File

@ -38,6 +38,7 @@ class MPPositive_SearchResultPreviewShowView: UIView, JXSegmentedListContainerVi
}
var scrollBlock:(() -> Void)?
var chooseMoreIndexBlock:((Int) -> Void)?
var moreBlock:((MPPositive_SearchResultItemViewModel) -> Void)?
//
var chooseItemBlock:((MPPositive_SearchResultItemViewModel) -> Void)?
init(frame: CGRect, sectionLists:[MPPositive_SearchResultListViewModel]) {
@ -85,6 +86,13 @@ extension MPPositive_SearchResultPreviewShowView:UITableViewDataSource, UITableV
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchResultShowTableViewCellID, for: indexPath) as! MPPositive_SearchResultShowTableViewCell
cell.itemView = sectionLists[indexPath.section].previewItemViews[indexPath.row]
cell.moreBlock = {
[weak self] in
guard let self = self else {return}
if moreBlock != nil {
moreBlock!(sectionLists[indexPath.section].previewItemViews[indexPath.row])
}
}
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

View File

@ -61,7 +61,7 @@ class MPPositive_SearchResultShowTableViewCell: UITableViewCell {
subtitleLabel.text = loadViewModel.subtitle
}
}
var moreBlock:(() -> Void)?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
@ -118,7 +118,9 @@ class MPPositive_SearchResultShowTableViewCell: UITableViewCell {
//
@objc private func moreActionClick(_ sender:UIButton) {
if moreBlock != nil {
moreBlock!()
}
}
//
@objc private func loadActionClick(_ sender:UIButton) {

View File

@ -55,7 +55,7 @@ class MPPositive_SearchResultTypeShowView: UIView, JXSegmentedListContainerViewL
}
}
var scrollBlock:(() -> Void)?
var moreBlock:((MPPositive_SearchResultItemViewModel) -> Void)?
init(frame: CGRect, list:MPPositive_SearchResultListViewModel) {
super.init(frame: frame)
backgroundColor = .init(hex: "1A1A1A")
@ -106,6 +106,13 @@ extension MPPositive_SearchResultTypeShowView:UITableViewDataSource, UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchResultShowTableViewCellID, for: indexPath) as! MPPositive_SearchResultShowTableViewCell
cell.itemView = sectionList.allItemViews[indexPath.row]
cell.moreBlock = {
[weak self] in
guard let self = self else {return}
if moreBlock != nil {
moreBlock!(sectionList.allItemViews[indexPath.row])
}
}
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {

View File

@ -15,17 +15,21 @@ class MPPositive_SearchResultsShowView: UIView {
[weak self] in
guard let self = self else {return}
if loadModel == nil {
isHidden = true
emptyImageView.isHidden = false
}else {
MP_AnalyticsManager.shared.search_result_pvAction()
loadModel.resultReloadBlock = {
[weak self] in
//
guard let self = self else {return}
MP_HUD.hideNow()
isHidden = false
MP_AnalyticsManager.shared.search_resultsuccess_actionAction()
dataSource.titles = loadModel?.sectionLists.compactMap({$0.title}) ?? []
dataSource.reloadData(selectedIndex: 0)
segmentView.reloadData()
emptyImageView.isHidden = !(dataSource.titles.count == 0)
}
}
}
@ -70,7 +74,7 @@ class MPPositive_SearchResultsShowView: UIView {
return listContainerView
}()
//
private lazy var emptyImageView:UIImageView = {
lazy var emptyImageView:UIImageView = {
let imageView = UIImageView(image: UIImage(named: "empty"))
imageView.contentMode = .scaleAspectFill
return imageView
@ -78,6 +82,7 @@ class MPPositive_SearchResultsShowView: UIView {
var scrollBlock:(() -> Void)?
//
var chooseItemBlock:((MPPositive_SearchResultItemViewModel) -> Void)?
var moreBlock:((MPPositive_SearchResultItemViewModel) -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .init(hex: "1A1A1A")
@ -144,6 +149,13 @@ extension MPPositive_SearchResultsShowView: JXSegmentedListContainerViewDataSour
chooseItemBlock!(item)
}
}
showView.moreBlock = {
[weak self] (itemView) in
guard let self = self else {return}
if moreBlock != nil {
moreBlock!(itemView)
}
}
return showView
}else {
//
@ -154,6 +166,13 @@ extension MPPositive_SearchResultsShowView: JXSegmentedListContainerViewDataSour
self?.scrollBlock!()
}
}
showView.moreBlock = {
[weak self] (itemView) in
guard let self = self else {return}
if moreBlock != nil {
moreBlock!(itemView)
}
}
return showView
}
}

View File

@ -10,34 +10,34 @@
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="MPSideA_AboutViewController" customModule="MusicPlayer" customModuleProvider="target">
<connections>
<outlet property="versionView" destination="4fU-O9-pas" id="cz8-Dk-J8d"/>
<outlet property="view" destination="Cvd-ZP-KOW" id="sSX-FH-ZL7"/>
<outlet property="versionView" destination="Wrk-iL-cGv" id="sjM-ZU-PyB"/>
<outlet property="view" destination="VwI-HU-Cye" id="KxG-DI-JCd"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="Cvd-ZP-KOW">
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="VwI-HU-Cye">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Set'mask" translatesAutoresizingMaskIntoConstraints="NO" id="be2-1M-VPB">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Set'mask" translatesAutoresizingMaskIntoConstraints="NO" id="31j-Zf-qLz">
<rect key="frame" x="0.0" y="0.0" width="375" height="691"/>
<constraints>
<constraint firstAttribute="height" constant="691" id="d8K-qP-7hT">
<constraint firstAttribute="height" constant="691" id="ES1-v2-JTn">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
</constraints>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="puq-JQ-VG5">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5gw-lg-ffB">
<rect key="frame" x="16" y="40" width="42" height="42"/>
<constraints>
<constraint firstAttribute="width" constant="42" id="Kld-pl-2rN">
<constraint firstAttribute="height" constant="42" id="6ds-ro-ddB">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstAttribute="height" constant="42" id="bc8-oI-D4b">
<constraint firstAttribute="width" constant="42" id="KQK-bd-ttH">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
@ -46,24 +46,24 @@
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" backgroundImage="Poplogo"/>
<connections>
<action selector="popClick:" destination="-1" eventType="touchUpInside" id="iM4-K0-ZQH"/>
<action selector="popClick:" destination="-1" eventType="touchUpInside" id="Unh-Z8-VGv"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="About" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YYU-ac-9gn">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="About" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Wzc-Lr-xPw">
<rect key="frame" x="159.5" y="49" width="56" height="24"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="20"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ICON" translatesAutoresizingMaskIntoConstraints="NO" id="0xx-Pi-pKv">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ICON" translatesAutoresizingMaskIntoConstraints="NO" id="bdA-Qf-Wki">
<rect key="frame" x="147.5" y="148" width="80" height="80"/>
<constraints>
<constraint firstAttribute="height" constant="80" id="L2T-XG-rgG">
<constraint firstAttribute="width" constant="80" id="6aP-Fe-KRS">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstAttribute="width" constant="80" id="zrl-r2-Cfr">
<constraint firstAttribute="height" constant="80" id="h6c-M8-B3C">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
@ -71,7 +71,7 @@
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
<real key="value" value="15"/>
<real key="value" value="12"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="borderColor">
<color key="value" red="0.50196078430000002" green="0.97647058819999999" blue="0.53333333329999999" alpha="0.75449892240000005" colorSpace="custom" customColorSpace="sRGB"/>
@ -81,31 +81,16 @@
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="MUSICOO'logo" translatesAutoresizingMaskIntoConstraints="NO" id="WxR-ti-5D3">
<rect key="frame" x="125.5" y="244" width="124" height="19"/>
<constraints>
<constraint firstAttribute="width" constant="124" id="7cc-mp-PhU">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstAttribute="height" constant="19" id="rBg-ye-ZbS">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
</constraints>
</imageView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4fU-O9-pas">
<rect key="frame" x="0.0" y="303" width="375" height="60"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Wrk-iL-cGv">
<rect key="frame" x="0.0" y="296" width="375" height="60"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Current Version" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zjg-cK-5H2">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Current Version" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fvv-Xp-KVp">
<rect key="frame" x="15" y="22.5" width="90.5" height="15"/>
<fontDescription key="fontDescription" name="Helvetica" family="Helvetica" pointSize="13"/>
<color key="textColor" white="1" alpha="0.40000000000000002" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version 1.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Aeb-L1-sB0">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version 1.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="miJ-lx-eHK">
<rect key="frame" x="294" y="22" width="66" height="16"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="textColor" red="0.61960784310000006" green="0.61960784310000006" blue="0.61960784310000006" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -114,66 +99,75 @@
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="zjg-cK-5H2" firstAttribute="centerY" secondItem="4fU-O9-pas" secondAttribute="centerY" id="Tls-lR-3ux"/>
<constraint firstItem="zjg-cK-5H2" firstAttribute="leading" secondItem="4fU-O9-pas" secondAttribute="leading" constant="15" id="Tn4-xz-DYE">
<constraint firstAttribute="trailing" secondItem="miJ-lx-eHK" secondAttribute="trailing" constant="15" id="844-fM-WCc">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstAttribute="height" constant="60" id="i7v-sK-pM6">
<constraint firstItem="fvv-Xp-KVp" firstAttribute="leading" secondItem="Wrk-iL-cGv" secondAttribute="leading" constant="15" id="9oY-vh-dne">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstItem="Aeb-L1-sB0" firstAttribute="centerY" secondItem="4fU-O9-pas" secondAttribute="centerY" id="mhe-B3-Ieg"/>
<constraint firstAttribute="trailing" secondItem="Aeb-L1-sB0" secondAttribute="trailing" constant="15" id="oBb-b6-a4B">
<constraint firstItem="miJ-lx-eHK" firstAttribute="centerY" secondItem="Wrk-iL-cGv" secondAttribute="centerY" id="Dr2-lQ-0pR"/>
<constraint firstItem="fvv-Xp-KVp" firstAttribute="centerY" secondItem="Wrk-iL-cGv" secondAttribute="centerY" id="kDT-hS-DXa"/>
<constraint firstAttribute="height" constant="60" id="yCe-QZ-3sw">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Musiclax" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Zfn-fz-UtF">
<rect key="frame" x="127" y="240" width="121" height="36"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="30"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="xLY-Ag-RuS"/>
<viewLayoutGuide key="safeArea" id="UZJ-Fu-b9f"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="puq-JQ-VG5" firstAttribute="top" secondItem="xLY-Ag-RuS" secondAttribute="top" constant="20" id="1CC-el-OFh">
<constraint firstAttribute="trailing" secondItem="31j-Zf-qLz" secondAttribute="trailing" id="1SW-Lv-Whj"/>
<constraint firstItem="Wzc-Lr-xPw" firstAttribute="centerX" secondItem="VwI-HU-Cye" secondAttribute="centerX" id="25V-9B-pbe"/>
<constraint firstItem="31j-Zf-qLz" firstAttribute="leading" secondItem="VwI-HU-Cye" secondAttribute="leading" id="2tk-qP-f0r"/>
<constraint firstItem="bdA-Qf-Wki" firstAttribute="centerX" secondItem="VwI-HU-Cye" secondAttribute="centerX" id="CBC-w0-cBQ"/>
<constraint firstItem="bdA-Qf-Wki" firstAttribute="top" secondItem="Wzc-Lr-xPw" secondAttribute="bottom" constant="75" id="IVf-Cn-uxv">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstItem="4fU-O9-pas" firstAttribute="top" secondItem="WxR-ti-5D3" secondAttribute="bottom" constant="40" id="3Hk-O3-plC"/>
<constraint firstItem="be2-1M-VPB" firstAttribute="leading" secondItem="Cvd-ZP-KOW" secondAttribute="leading" id="9tf-ol-TR5"/>
<constraint firstItem="YYU-ac-9gn" firstAttribute="centerX" secondItem="Cvd-ZP-KOW" secondAttribute="centerX" id="H5x-yw-4iD"/>
<constraint firstItem="0xx-Pi-pKv" firstAttribute="centerX" secondItem="Cvd-ZP-KOW" secondAttribute="centerX" id="K2R-cQ-Zag"/>
<constraint firstItem="be2-1M-VPB" firstAttribute="top" secondItem="Cvd-ZP-KOW" secondAttribute="top" id="KWI-v2-z9D"/>
<constraint firstItem="WxR-ti-5D3" firstAttribute="centerX" secondItem="Cvd-ZP-KOW" secondAttribute="centerX" id="Luo-1Z-TZx"/>
<constraint firstItem="4fU-O9-pas" firstAttribute="leading" secondItem="xLY-Ag-RuS" secondAttribute="leading" id="PFV-mq-OZO"/>
<constraint firstItem="puq-JQ-VG5" firstAttribute="leading" secondItem="xLY-Ag-RuS" secondAttribute="leading" constant="16" id="isA-2d-RPM">
<constraint firstItem="Wrk-iL-cGv" firstAttribute="trailing" secondItem="UZJ-Fu-b9f" secondAttribute="trailing" id="Vu6-Fm-dFV"/>
<constraint firstItem="Wrk-iL-cGv" firstAttribute="leading" secondItem="UZJ-Fu-b9f" secondAttribute="leading" id="Zo7-eO-o9U"/>
<constraint firstItem="Zfn-fz-UtF" firstAttribute="centerX" secondItem="bdA-Qf-Wki" secondAttribute="centerX" id="cTy-pH-dNH"/>
<constraint firstItem="Zfn-fz-UtF" firstAttribute="top" secondItem="bdA-Qf-Wki" secondAttribute="bottom" constant="12" id="dJs-I4-rFf">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstAttribute="trailing" secondItem="be2-1M-VPB" secondAttribute="trailing" id="jLP-Wn-udz"/>
<constraint firstItem="4fU-O9-pas" firstAttribute="trailing" secondItem="xLY-Ag-RuS" secondAttribute="trailing" id="qFp-0z-pS8"/>
<constraint firstItem="WxR-ti-5D3" firstAttribute="top" secondItem="0xx-Pi-pKv" secondAttribute="bottom" constant="16" id="qvN-fj-QEO">
<constraint firstItem="5gw-lg-ffB" firstAttribute="leading" secondItem="UZJ-Fu-b9f" secondAttribute="leading" constant="16" id="h5u-9Q-G99">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstItem="0xx-Pi-pKv" firstAttribute="top" secondItem="YYU-ac-9gn" secondAttribute="bottom" constant="75" id="ulb-gy-Y22">
<constraint firstItem="5gw-lg-ffB" firstAttribute="top" secondItem="UZJ-Fu-b9f" secondAttribute="top" constant="20" id="h74-pM-pC7">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstItem="YYU-ac-9gn" firstAttribute="centerY" secondItem="puq-JQ-VG5" secondAttribute="centerY" id="vRO-0c-Ywd"/>
<constraint firstItem="31j-Zf-qLz" firstAttribute="top" secondItem="VwI-HU-Cye" secondAttribute="top" id="jhp-qC-hsO"/>
<constraint firstItem="Wrk-iL-cGv" firstAttribute="top" secondItem="Zfn-fz-UtF" secondAttribute="bottom" constant="20" id="q9w-RQ-gNf">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="adapterScreen" value="YES"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstItem="Wzc-Lr-xPw" firstAttribute="centerY" secondItem="5gw-lg-ffB" secondAttribute="centerY" id="uR5-5r-KkI"/>
</constraints>
<point key="canvasLocation" x="130.40000000000001" y="-12.143928035982009"/>
</view>
</objects>
<resources>
<image name="ICON" width="1024" height="1024"/>
<image name="MUSICOO'logo" width="124" height="19"/>
<image name="ICON" width="256" height="256"/>
<image name="Poplogo" width="42" height="42"/>
<image name="Set'mask" width="375" height="691"/>
</resources>

View File

@ -47,9 +47,9 @@ extension MPSideA_SettingViewController: UITableViewDataSource, UITableViewDeleg
navigationController?.pushViewController(aboutVC, animated: true)
case 1://Feedback
let alert = UIAlertController(title: "Feedback", message: "If you have any comments or suggestions, please contact us at the following e-mail address", preferredStyle: .actionSheet)
let email = UIAlertAction(title: "support@musicoo.app", style: .default) { (_) in
let email = UIAlertAction(title: "marketing@lux-ad.com", style: .default) { (_) in
//
UIPasteboard.general.string = "support@musicoo.app"
UIPasteboard.general.string = "marketing@lux-ad.com"
MP_HUD.text("Successfully copied the e-mail address to the clipboard", delay: 1.0, completion: nil)
}
alert.addAction(email)
@ -62,7 +62,7 @@ extension MPSideA_SettingViewController: UITableViewDataSource, UITableViewDeleg
//icon
let image = UIImage(named: "ICON")
//
let url = URL(string: "https://musicoo.app/")
let url = URL(string: "https://musiclax.mystrikingly.com/")
let activityItems = [text,image as Any,url as Any]
//
let activityViewController = UIActivityViewController(activityItems: activityItems, applicationActivities:nil)

View File

@ -53,7 +53,7 @@ class MPSideA_HomeViewController: MPSideA_BaseViewController {
super.viewWillDisappear(animated)
//
NotificationCenter.default.removeObserver(self)
MP_AnalyticsManager.shared.home_a_pvAction()
}
deinit {

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>3B52.1</string>
</array>
</dict>
</array>
</dict>
</plist>