播放详情页面,选集功能,播放速率调整
@ -77,6 +77,26 @@
|
|||||||
BF0FA73F2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA73E2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift */; };
|
BF0FA73F2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA73E2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift */; };
|
||||||
BF0FA7412DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7402DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift */; };
|
BF0FA7412DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7402DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift */; };
|
||||||
BF0FA7452DDF027900C9E5F2 /* VPPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7442DDF027900C9E5F2 /* VPPlayer.swift */; };
|
BF0FA7452DDF027900C9E5F2 /* VPPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7442DDF027900C9E5F2 /* VPPlayer.swift */; };
|
||||||
|
BF0FA74A2DDF04E200C9E5F2 /* VPPlayerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7492DDF04E200C9E5F2 /* VPPlayerProtocol.swift */; };
|
||||||
|
BF0FA74C2DDF060200C9E5F2 /* VPVideoPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA74B2DDF060200C9E5F2 /* VPVideoPlayerViewController.swift */; };
|
||||||
|
BF0FA74E2DDF067E00C9E5F2 /* VPVideoPlayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA74D2DDF067E00C9E5F2 /* VPVideoPlayViewModel.swift */; };
|
||||||
|
BF0FA7502DDF0A9900C9E5F2 /* VPVideoPlayerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA74F2DDF0A9900C9E5F2 /* VPVideoPlayerCell.swift */; };
|
||||||
|
BF0FA7522DDF134700C9E5F2 /* VPVideoPlayerControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7512DDF134700C9E5F2 /* VPVideoPlayerControlView.swift */; };
|
||||||
|
BF0FA7572DDF159B00C9E5F2 /* VPExploreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7562DDF159A00C9E5F2 /* VPExploreViewController.swift */; };
|
||||||
|
BF0FA7592DDF1C2800C9E5F2 /* VPPlayerProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7582DDF1C2800C9E5F2 /* VPPlayerProgressView.swift */; };
|
||||||
|
BF0FA75B2DDF206000C9E5F2 /* VPExplorePlayerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA75A2DDF206000C9E5F2 /* VPExplorePlayerCell.swift */; };
|
||||||
|
BF0FA75D2DDF208400C9E5F2 /* VPExplorePlayerControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA75C2DDF208400C9E5F2 /* VPExplorePlayerControlView.swift */; };
|
||||||
|
BF0FA75F2DDFFDB000C9E5F2 /* VPDetailPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA75E2DDFFDB000C9E5F2 /* VPDetailPlayerViewController.swift */; };
|
||||||
|
BF0FA7612DDFFE7100C9E5F2 /* VPVideoDetailModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7602DDFFE7100C9E5F2 /* VPVideoDetailModel.swift */; };
|
||||||
|
BF0FA7632DE006E700C9E5F2 /* VPDetailPlayerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7622DE006E700C9E5F2 /* VPDetailPlayerCell.swift */; };
|
||||||
|
BF0FA7652DE00A0E00C9E5F2 /* VPDetailPlayerControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7642DE00A0E00C9E5F2 /* VPDetailPlayerControlView.swift */; };
|
||||||
|
BF0FA7672DE0469300C9E5F2 /* VPEpisodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7662DE0469300C9E5F2 /* VPEpisodeView.swift */; };
|
||||||
|
BF0FA7692DE0502900C9E5F2 /* VPEpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7682DE0502900C9E5F2 /* VPEpisodeCell.swift */; };
|
||||||
|
BF0FA76B2DE0533400C9E5F2 /* VPEpisodeMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA76A2DE0533400C9E5F2 /* VPEpisodeMenuView.swift */; };
|
||||||
|
BF0FA76D2DE053C100C9E5F2 /* VPScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA76C2DE053C100C9E5F2 /* VPScrollView.swift */; };
|
||||||
|
BF0FA76F2DE062A700C9E5F2 /* VPRateSelectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA76E2DE062A700C9E5F2 /* VPRateSelectedView.swift */; };
|
||||||
|
BF0FA7712DE062EB00C9E5F2 /* VPVideoRateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7702DE062EB00C9E5F2 /* VPVideoRateModel.swift */; };
|
||||||
|
BF0FA7732DE0671200C9E5F2 /* VPRateSelectedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7722DE0671200C9E5F2 /* VPRateSelectedCell.swift */; };
|
||||||
F939C04AD4003BA127F15C28 /* Pods_Veloria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */; };
|
F939C04AD4003BA127F15C28 /* Pods_Veloria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@ -160,6 +180,26 @@
|
|||||||
BF0FA73E2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeSearchButton.swift; sourceTree = "<group>"; };
|
BF0FA73E2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeSearchButton.swift; sourceTree = "<group>"; };
|
||||||
BF0FA7402DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+VPRefresh.swift"; sourceTree = "<group>"; };
|
BF0FA7402DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+VPRefresh.swift"; sourceTree = "<group>"; };
|
||||||
BF0FA7442DDF027900C9E5F2 /* VPPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPPlayer.swift; sourceTree = "<group>"; };
|
BF0FA7442DDF027900C9E5F2 /* VPPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPPlayer.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7492DDF04E200C9E5F2 /* VPPlayerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPPlayerProtocol.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA74B2DDF060200C9E5F2 /* VPVideoPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoPlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA74D2DDF067E00C9E5F2 /* VPVideoPlayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoPlayViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA74F2DDF0A9900C9E5F2 /* VPVideoPlayerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoPlayerCell.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7512DDF134700C9E5F2 /* VPVideoPlayerControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoPlayerControlView.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7562DDF159A00C9E5F2 /* VPExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPExploreViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7582DDF1C2800C9E5F2 /* VPPlayerProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPPlayerProgressView.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA75A2DDF206000C9E5F2 /* VPExplorePlayerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPExplorePlayerCell.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA75C2DDF208400C9E5F2 /* VPExplorePlayerControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPExplorePlayerControlView.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA75E2DDFFDB000C9E5F2 /* VPDetailPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPDetailPlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7602DDFFE7100C9E5F2 /* VPVideoDetailModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoDetailModel.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7622DE006E700C9E5F2 /* VPDetailPlayerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPDetailPlayerCell.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7642DE00A0E00C9E5F2 /* VPDetailPlayerControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPDetailPlayerControlView.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7662DE0469300C9E5F2 /* VPEpisodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPEpisodeView.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7682DE0502900C9E5F2 /* VPEpisodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPEpisodeCell.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA76A2DE0533400C9E5F2 /* VPEpisodeMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPEpisodeMenuView.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA76C2DE053C100C9E5F2 /* VPScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPScrollView.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA76E2DE062A700C9E5F2 /* VPRateSelectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPRateSelectedView.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7702DE062EB00C9E5F2 /* VPVideoRateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoRateModel.swift; sourceTree = "<group>"; };
|
||||||
|
BF0FA7722DE0671200C9E5F2 /* VPRateSelectedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPRateSelectedCell.swift; sourceTree = "<group>"; };
|
||||||
E0BDA3570E00C90877E45AA0 /* Pods-VideoPlayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VideoPlayer.debug.xcconfig"; path = "Target Support Files/Pods-VideoPlayer/Pods-VideoPlayer.debug.xcconfig"; sourceTree = "<group>"; };
|
E0BDA3570E00C90877E45AA0 /* Pods-VideoPlayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VideoPlayer.debug.xcconfig"; path = "Target Support Files/Pods-VideoPlayer/Pods-VideoPlayer.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@ -283,6 +323,7 @@
|
|||||||
BF0FA71A2DDC7FF200C9E5F2 /* VPImageView.swift */,
|
BF0FA71A2DDC7FF200C9E5F2 /* VPImageView.swift */,
|
||||||
BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */,
|
BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */,
|
||||||
BF0FA7272DDC91F800C9E5F2 /* VPCollectionViewCell.swift */,
|
BF0FA7272DDC91F800C9E5F2 /* VPCollectionViewCell.swift */,
|
||||||
|
BF0FA76C2DE053C100C9E5F2 /* VPScrollView.swift */,
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -474,6 +515,7 @@
|
|||||||
BF0FA7472DDF03B600C9E5F2 /* Controller */,
|
BF0FA7472DDF03B600C9E5F2 /* Controller */,
|
||||||
BF0FA7462DDF03AD00C9E5F2 /* View */,
|
BF0FA7462DDF03AD00C9E5F2 /* View */,
|
||||||
BF0FA6FE2DDC660300C9E5F2 /* Model */,
|
BF0FA6FE2DDC660300C9E5F2 /* Model */,
|
||||||
|
BF0FA7482DDF04B800C9E5F2 /* ViewModel */,
|
||||||
);
|
);
|
||||||
path = Player;
|
path = Player;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -481,8 +523,11 @@
|
|||||||
BF0FA6FE2DDC660300C9E5F2 /* Model */ = {
|
BF0FA6FE2DDC660300C9E5F2 /* Model */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
BF0FA7492DDF04E200C9E5F2 /* VPPlayerProtocol.swift */,
|
||||||
BF0FA6FF2DDC665300C9E5F2 /* VPShortModel.swift */,
|
BF0FA6FF2DDC665300C9E5F2 /* VPShortModel.swift */,
|
||||||
BF0FA7012DDC667C00C9E5F2 /* VPVideoInfoModel.swift */,
|
BF0FA7012DDC667C00C9E5F2 /* VPVideoInfoModel.swift */,
|
||||||
|
BF0FA7602DDFFE7100C9E5F2 /* VPVideoDetailModel.swift */,
|
||||||
|
BF0FA7702DE062EB00C9E5F2 /* VPVideoRateModel.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -539,6 +584,9 @@
|
|||||||
BF0FA7422DDF024400C9E5F2 /* Explore */ = {
|
BF0FA7422DDF024400C9E5F2 /* Explore */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
BF0FA7552DDF158000C9E5F2 /* Controller */,
|
||||||
|
BF0FA7542DDF157700C9E5F2 /* View */,
|
||||||
|
BF0FA7532DDF156F00C9E5F2 /* Model */,
|
||||||
);
|
);
|
||||||
path = Explore;
|
path = Explore;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -554,6 +602,16 @@
|
|||||||
BF0FA7462DDF03AD00C9E5F2 /* View */ = {
|
BF0FA7462DDF03AD00C9E5F2 /* View */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
BF0FA74F2DDF0A9900C9E5F2 /* VPVideoPlayerCell.swift */,
|
||||||
|
BF0FA7512DDF134700C9E5F2 /* VPVideoPlayerControlView.swift */,
|
||||||
|
BF0FA7582DDF1C2800C9E5F2 /* VPPlayerProgressView.swift */,
|
||||||
|
BF0FA7622DE006E700C9E5F2 /* VPDetailPlayerCell.swift */,
|
||||||
|
BF0FA7642DE00A0E00C9E5F2 /* VPDetailPlayerControlView.swift */,
|
||||||
|
BF0FA7662DE0469300C9E5F2 /* VPEpisodeView.swift */,
|
||||||
|
BF0FA7682DE0502900C9E5F2 /* VPEpisodeCell.swift */,
|
||||||
|
BF0FA76A2DE0533400C9E5F2 /* VPEpisodeMenuView.swift */,
|
||||||
|
BF0FA76E2DE062A700C9E5F2 /* VPRateSelectedView.swift */,
|
||||||
|
BF0FA7722DE0671200C9E5F2 /* VPRateSelectedCell.swift */,
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -561,6 +619,40 @@
|
|||||||
BF0FA7472DDF03B600C9E5F2 /* Controller */ = {
|
BF0FA7472DDF03B600C9E5F2 /* Controller */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
BF0FA74B2DDF060200C9E5F2 /* VPVideoPlayerViewController.swift */,
|
||||||
|
BF0FA75E2DDFFDB000C9E5F2 /* VPDetailPlayerViewController.swift */,
|
||||||
|
);
|
||||||
|
path = Controller;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
BF0FA7482DDF04B800C9E5F2 /* ViewModel */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BF0FA74D2DDF067E00C9E5F2 /* VPVideoPlayViewModel.swift */,
|
||||||
|
);
|
||||||
|
path = ViewModel;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
BF0FA7532DDF156F00C9E5F2 /* Model */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
path = Model;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
BF0FA7542DDF157700C9E5F2 /* View */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BF0FA75A2DDF206000C9E5F2 /* VPExplorePlayerCell.swift */,
|
||||||
|
BF0FA75C2DDF208400C9E5F2 /* VPExplorePlayerControlView.swift */,
|
||||||
|
);
|
||||||
|
path = View;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
BF0FA7552DDF158000C9E5F2 /* Controller */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
BF0FA7562DDF159A00C9E5F2 /* VPExploreViewController.swift */,
|
||||||
);
|
);
|
||||||
path = Controller;
|
path = Controller;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -686,6 +778,7 @@
|
|||||||
files = (
|
files = (
|
||||||
1B056E742DDB2DD7007EE38D /* UIView+VPAdd.swift in Sources */,
|
1B056E742DDB2DD7007EE38D /* UIView+VPAdd.swift in Sources */,
|
||||||
BF0FA6F12DDC600200C9E5F2 /* VPNetwork.swift in Sources */,
|
BF0FA6F12DDC600200C9E5F2 /* VPNetwork.swift in Sources */,
|
||||||
|
BF0FA7502DDF0A9900C9E5F2 /* VPVideoPlayerCell.swift in Sources */,
|
||||||
1B056E572DDACC6B007EE38D /* VPHomePageViewController.swift in Sources */,
|
1B056E572DDACC6B007EE38D /* VPHomePageViewController.swift in Sources */,
|
||||||
BF0FA72A2DDC922F00C9E5F2 /* VPHomeRecommandCell.swift in Sources */,
|
BF0FA72A2DDC922F00C9E5F2 /* VPHomeRecommandCell.swift in Sources */,
|
||||||
BF0FA7322DDEBD6400C9E5F2 /* AppDelegate+Config.swift in Sources */,
|
BF0FA7322DDEBD6400C9E5F2 /* AppDelegate+Config.swift in Sources */,
|
||||||
@ -695,6 +788,7 @@
|
|||||||
BF0FA7262DDC8F7600C9E5F2 /* VPCollectionView.swift in Sources */,
|
BF0FA7262DDC8F7600C9E5F2 /* VPCollectionView.swift in Sources */,
|
||||||
BF0FA71D2DDC807200C9E5F2 /* UIImageView+VPAdd.swift in Sources */,
|
BF0FA71D2DDC807200C9E5F2 /* UIImageView+VPAdd.swift in Sources */,
|
||||||
BF0FA6DC2DDC5CD700C9E5F2 /* VPTokenModel.swift in Sources */,
|
BF0FA6DC2DDC5CD700C9E5F2 /* VPTokenModel.swift in Sources */,
|
||||||
|
BF0FA7572DDF159B00C9E5F2 /* VPExploreViewController.swift in Sources */,
|
||||||
1B056E772DDB3641007EE38D /* VPTabBarItemNormalVew.swift in Sources */,
|
1B056E772DDB3641007EE38D /* VPTabBarItemNormalVew.swift in Sources */,
|
||||||
1B056E4B2DDAC6BA007EE38D /* VPDefine.swift in Sources */,
|
1B056E4B2DDAC6BA007EE38D /* VPDefine.swift in Sources */,
|
||||||
1B056E702DDB019B007EE38D /* VPTabBarItemContainer.swift in Sources */,
|
1B056E702DDB019B007EE38D /* VPTabBarItemContainer.swift in Sources */,
|
||||||
@ -703,22 +797,33 @@
|
|||||||
1B056E6C2DDADAA1007EE38D /* VPTabBar.swift in Sources */,
|
1B056E6C2DDADAA1007EE38D /* VPTabBar.swift in Sources */,
|
||||||
BF0FA72E2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift in Sources */,
|
BF0FA72E2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift in Sources */,
|
||||||
BF0FA7302DDEBB1600C9E5F2 /* UIButton+VPAdd.swift in Sources */,
|
BF0FA7302DDEBB1600C9E5F2 /* UIButton+VPAdd.swift in Sources */,
|
||||||
|
BF0FA74C2DDF060200C9E5F2 /* VPVideoPlayerViewController.swift in Sources */,
|
||||||
|
BF0FA76D2DE053C100C9E5F2 /* VPScrollView.swift in Sources */,
|
||||||
1B056E5D2DDACD8E007EE38D /* UIFont+VPAdd.swift in Sources */,
|
1B056E5D2DDACD8E007EE38D /* UIFont+VPAdd.swift in Sources */,
|
||||||
BF0FA7282DDC91F800C9E5F2 /* VPCollectionViewCell.swift in Sources */,
|
BF0FA7282DDC91F800C9E5F2 /* VPCollectionViewCell.swift in Sources */,
|
||||||
|
BF0FA7672DE0469300C9E5F2 /* VPEpisodeView.swift in Sources */,
|
||||||
BF0FA6F92DDC64E700C9E5F2 /* VPHomeAPI.swift in Sources */,
|
BF0FA6F92DDC64E700C9E5F2 /* VPHomeAPI.swift in Sources */,
|
||||||
BF0FA6F42DDC604500C9E5F2 /* VPHUD.swift in Sources */,
|
BF0FA6F42DDC604500C9E5F2 /* VPHUD.swift in Sources */,
|
||||||
BF0FA7222DDC859D00C9E5F2 /* NSNumber+VPAdd.swift in Sources */,
|
BF0FA7222DDC859D00C9E5F2 /* NSNumber+VPAdd.swift in Sources */,
|
||||||
BF0FA73B2DDED1C700C9E5F2 /* VPHomeListCell.swift in Sources */,
|
BF0FA73B2DDED1C700C9E5F2 /* VPHomeListCell.swift in Sources */,
|
||||||
|
BF0FA7592DDF1C2800C9E5F2 /* VPPlayerProgressView.swift in Sources */,
|
||||||
BF0FA71B2DDC7FF200C9E5F2 /* VPImageView.swift in Sources */,
|
BF0FA71B2DDC7FF200C9E5F2 /* VPImageView.swift in Sources */,
|
||||||
|
BF0FA7522DDF134700C9E5F2 /* VPVideoPlayerControlView.swift in Sources */,
|
||||||
1B056E2C2DDAC0FD007EE38D /* SceneDelegate.swift in Sources */,
|
1B056E2C2DDAC0FD007EE38D /* SceneDelegate.swift in Sources */,
|
||||||
|
BF0FA75D2DDF208400C9E5F2 /* VPExplorePlayerControlView.swift in Sources */,
|
||||||
1B056E462DDAC370007EE38D /* UIScreen+VPAdd.swift in Sources */,
|
1B056E462DDAC370007EE38D /* UIScreen+VPAdd.swift in Sources */,
|
||||||
|
BF0FA7692DE0502900C9E5F2 /* VPEpisodeCell.swift in Sources */,
|
||||||
BF0FA73D2DDED2D000C9E5F2 /* VPVideoAPI.swift in Sources */,
|
BF0FA73D2DDED2D000C9E5F2 /* VPVideoAPI.swift in Sources */,
|
||||||
BF0FA6EE2DDC5F8700C9E5F2 /* JXUUID.m in Sources */,
|
BF0FA6EE2DDC5F8700C9E5F2 /* JXUUID.m in Sources */,
|
||||||
BF0FA7052DDC67AC00C9E5F2 /* VPHomeViewModel.swift in Sources */,
|
BF0FA7052DDC67AC00C9E5F2 /* VPHomeViewModel.swift in Sources */,
|
||||||
BF0FA6EF2DDC5F8700C9E5F2 /* PDKeyChain.m in Sources */,
|
BF0FA6EF2DDC5F8700C9E5F2 /* PDKeyChain.m in Sources */,
|
||||||
|
BF0FA75F2DDFFDB000C9E5F2 /* VPDetailPlayerViewController.swift in Sources */,
|
||||||
|
BF0FA7732DE0671200C9E5F2 /* VPRateSelectedCell.swift in Sources */,
|
||||||
1B056E4D2DDAC7C1007EE38D /* VPTabBarController.swift in Sources */,
|
1B056E4D2DDAC7C1007EE38D /* VPTabBarController.swift in Sources */,
|
||||||
BF0FA6DA2DDC5CB600C9E5F2 /* VPLoginManager.swift in Sources */,
|
BF0FA6DA2DDC5CB600C9E5F2 /* VPLoginManager.swift in Sources */,
|
||||||
|
BF0FA74A2DDF04E200C9E5F2 /* VPPlayerProtocol.swift in Sources */,
|
||||||
BF0FA72C2DDD7B7300C9E5F2 /* VPHomeRankingContentCell.swift in Sources */,
|
BF0FA72C2DDD7B7300C9E5F2 /* VPHomeRankingContentCell.swift in Sources */,
|
||||||
|
BF0FA7652DE00A0E00C9E5F2 /* VPDetailPlayerControlView.swift in Sources */,
|
||||||
BF0FA7342DDEC74500C9E5F2 /* VPCategoryModel.swift in Sources */,
|
BF0FA7342DDEC74500C9E5F2 /* VPCategoryModel.swift in Sources */,
|
||||||
1B056E4F2DDAC7FC007EE38D /* VPNavigationController.swift in Sources */,
|
1B056E4F2DDAC7FC007EE38D /* VPNavigationController.swift in Sources */,
|
||||||
1B056E3F2DDAC2DB007EE38D /* VPCryptorService.swift in Sources */,
|
1B056E3F2DDAC2DB007EE38D /* VPCryptorService.swift in Sources */,
|
||||||
@ -728,25 +833,32 @@
|
|||||||
BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */,
|
BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */,
|
||||||
BF0FA6D72DDC5BE100C9E5F2 /* VPURLPath.swift in Sources */,
|
BF0FA6D72DDC5BE100C9E5F2 /* VPURLPath.swift in Sources */,
|
||||||
1B056E5B2DDACD80007EE38D /* UIColor+VPAdd.swift in Sources */,
|
1B056E5B2DDACD80007EE38D /* UIColor+VPAdd.swift in Sources */,
|
||||||
|
BF0FA76B2DE0533400C9E5F2 /* VPEpisodeMenuView.swift in Sources */,
|
||||||
1B056E6A2DDAD0BF007EE38D /* VPLocalizedManager.swift in Sources */,
|
1B056E6A2DDAD0BF007EE38D /* VPLocalizedManager.swift in Sources */,
|
||||||
BF0FA7242DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift in Sources */,
|
BF0FA7242DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift in Sources */,
|
||||||
BF0FA7192DDC7F4900C9E5F2 /* VPHomeBannerCell.swift in Sources */,
|
BF0FA7192DDC7F4900C9E5F2 /* VPHomeBannerCell.swift in Sources */,
|
||||||
|
BF0FA7712DE062EB00C9E5F2 /* VPVideoRateModel.swift in Sources */,
|
||||||
BF0FA7022DDC667C00C9E5F2 /* VPVideoInfoModel.swift in Sources */,
|
BF0FA7022DDC667C00C9E5F2 /* VPVideoInfoModel.swift in Sources */,
|
||||||
|
BF0FA76F2DE062A700C9E5F2 /* VPRateSelectedView.swift in Sources */,
|
||||||
1B056E792DDB365A007EE38D /* VPTabBarItemSelectedView.swift in Sources */,
|
1B056E792DDB365A007EE38D /* VPTabBarItemSelectedView.swift in Sources */,
|
||||||
1B056E722DDB022F007EE38D /* VPTabBarItem.swift in Sources */,
|
1B056E722DDB022F007EE38D /* VPTabBarItem.swift in Sources */,
|
||||||
1B056E412DDAC30A007EE38D /* VPModel.swift in Sources */,
|
1B056E412DDAC30A007EE38D /* VPModel.swift in Sources */,
|
||||||
BF0FA70A2DDC69C800C9E5F2 /* VPTableViewCell.swift in Sources */,
|
BF0FA70A2DDC69C800C9E5F2 /* VPTableViewCell.swift in Sources */,
|
||||||
1B056E442DDAC355007EE38D /* UIDevice+VPAdd.swift in Sources */,
|
1B056E442DDAC355007EE38D /* UIDevice+VPAdd.swift in Sources */,
|
||||||
|
BF0FA7632DE006E700C9E5F2 /* VPDetailPlayerCell.swift in Sources */,
|
||||||
BF0FA73F2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift in Sources */,
|
BF0FA73F2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift in Sources */,
|
||||||
1B056E512DDACBE5007EE38D /* VPViewController.swift in Sources */,
|
1B056E512DDACBE5007EE38D /* VPViewController.swift in Sources */,
|
||||||
BF0FA7122DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift in Sources */,
|
BF0FA7122DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift in Sources */,
|
||||||
BF0FA7162DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift in Sources */,
|
BF0FA7162DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift in Sources */,
|
||||||
BF0FA7172DDC78FF00C9E5F2 /* ZKCycleScrollView.swift in Sources */,
|
BF0FA7172DDC78FF00C9E5F2 /* ZKCycleScrollView.swift in Sources */,
|
||||||
|
BF0FA7612DDFFE7100C9E5F2 /* VPVideoDetailModel.swift in Sources */,
|
||||||
BF0FA6D52DDC5B5D00C9E5F2 /* VPApi.swift in Sources */,
|
BF0FA6D52DDC5B5D00C9E5F2 /* VPApi.swift in Sources */,
|
||||||
BF0FA6FC2DDC657500C9E5F2 /* VPHomeDataModel.swift in Sources */,
|
BF0FA6FC2DDC657500C9E5F2 /* VPHomeDataModel.swift in Sources */,
|
||||||
BF0FA7392DDECF8900C9E5F2 /* VPHomeListViewController.swift in Sources */,
|
BF0FA7392DDECF8900C9E5F2 /* VPHomeListViewController.swift in Sources */,
|
||||||
BF0FA6F62DDC616300C9E5F2 /* VPToast.swift in Sources */,
|
BF0FA6F62DDC616300C9E5F2 /* VPToast.swift in Sources */,
|
||||||
1B056E492DDAC3DF007EE38D /* VPAppTool.swift in Sources */,
|
1B056E492DDAC3DF007EE38D /* VPAppTool.swift in Sources */,
|
||||||
|
BF0FA74E2DDF067E00C9E5F2 /* VPVideoPlayViewModel.swift in Sources */,
|
||||||
|
BF0FA75B2DDF206000C9E5F2 /* VPExplorePlayerCell.swift in Sources */,
|
||||||
BF0FA7412DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift in Sources */,
|
BF0FA7412DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift in Sources */,
|
||||||
BF0FA70C2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift in Sources */,
|
BF0FA70C2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift in Sources */,
|
||||||
BF0FA7102DDC6CA200C9E5F2 /* VPListModel.swift in Sources */,
|
BF0FA7102DDC6CA200C9E5F2 /* VPListModel.swift in Sources */,
|
||||||
|
@ -10,7 +10,8 @@ import UIKit
|
|||||||
extension AppDelegate {
|
extension AppDelegate {
|
||||||
|
|
||||||
func appConfig() {
|
func appConfig() {
|
||||||
UIButton.vp_Awake()
|
UIButton.vp_bt_Awake()
|
||||||
|
UIView.vp_Awake()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ class VPNavigationController: UINavigationController {
|
|||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
// self.interactivePopGestureRecognizer?.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
|
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
|
||||||
|
@ -87,7 +87,7 @@ extension VPTabBarController {
|
|||||||
|
|
||||||
|
|
||||||
let nav1 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected"))
|
let nav1 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected"))
|
||||||
let nav2 = createNavigationController(viewController: VPHomePageViewController(), title: "Explore".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))
|
let nav2 = createNavigationController(viewController: VPExploreViewController(), title: "Explore".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))
|
||||||
let nav3 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected"))
|
let nav3 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected"))
|
||||||
let nav4 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_04_selected"))
|
let nav4 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_04_selected"))
|
||||||
|
|
||||||
|
@ -14,11 +14,22 @@ class VPViewController: UIViewController {
|
|||||||
return imageView
|
return imageView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
private(set) var isViewDidLoad = false
|
||||||
|
private(set) var isDidAppear = false
|
||||||
|
private(set) var hasViewDidAppear = false
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
self.isViewDidLoad = true
|
||||||
view.backgroundColor = .backgroundColor()
|
view.backgroundColor = .backgroundColor()
|
||||||
|
|
||||||
|
if let navi = navigationController {
|
||||||
|
if navi.visibleViewController == self {
|
||||||
|
if navi.viewControllers.count > 1 {
|
||||||
|
configNavigationBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
view.addSubview(bgImageView)
|
view.addSubview(bgImageView)
|
||||||
|
|
||||||
@ -27,9 +38,57 @@ class VPViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
isDidAppear = true
|
||||||
|
hasViewDidAppear = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
isDidAppear = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
isDidAppear = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||||
|
return .lightContent
|
||||||
|
}
|
||||||
|
|
||||||
func handleHeaderRefresh(_ completer: (() -> Void)?) {}
|
func handleHeaderRefresh(_ completer: (() -> Void)?) {}
|
||||||
|
|
||||||
func handleFooterRefresh(_ completer: (() -> Void)?) {}
|
func handleFooterRefresh(_ completer: (() -> Void)?) {}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension VPViewController {
|
||||||
|
func configNavigationBack(_ imageName: String = "arrow_left_icon_01") {
|
||||||
|
let image = UIImage(named: imageName)
|
||||||
|
|
||||||
|
let leftBarButtonItem = UIBarButtonItem(image: image, style: .plain ,target: self,action: #selector(handleBack))
|
||||||
|
navigationItem.leftBarButtonItem = leftBarButtonItem
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleBack() {
|
||||||
|
self.vp_toLastViewController(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func vp_toLastViewController(animated: Bool) {
|
||||||
|
if self.navigationController != nil
|
||||||
|
{
|
||||||
|
if self.navigationController?.viewControllers.count == 1
|
||||||
|
{
|
||||||
|
self.dismiss(animated: animated, completion: nil)
|
||||||
|
} else {
|
||||||
|
self.navigationController?.popViewController(animated: animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if self.presentingViewController != nil {
|
||||||
|
self.dismiss(animated: animated, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,3 +23,19 @@ extension NSNumber {
|
|||||||
return formatter.string(from: self) ?? "0"
|
return formatter.string(from: self) ?? "0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension Int {
|
||||||
|
func formatTimeGroup() -> (String, String, String) {
|
||||||
|
let seconds = self
|
||||||
|
|
||||||
|
var s: String = "00"
|
||||||
|
var m: String = "00"
|
||||||
|
var h: String = "00"
|
||||||
|
s = String(format: "%02d", Int(Int(seconds) % 60))
|
||||||
|
m = String(format: "%02d", Int(seconds / 60) % 60)
|
||||||
|
h = String(format: "%02d", Int(seconds / 3600))
|
||||||
|
|
||||||
|
return (h, m, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,23 +9,23 @@ import UIKit
|
|||||||
|
|
||||||
extension UIButton {
|
extension UIButton {
|
||||||
fileprivate struct AssociatedKeys {
|
fileprivate struct AssociatedKeys {
|
||||||
static var vp_gradientLayer: Int?
|
static var bt_gradientLayer: Int?
|
||||||
static var vp_gradientBorder: Int?
|
static var bt_gradientBorder: Int?
|
||||||
static var vp_gradientBorderShapeLayer: Int?
|
static var bt_gradientBorderShapeLayer: Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@objc public static func vp_Awake() {
|
@objc public static func vp_bt_Awake() {
|
||||||
vp_swizzled_instanceMethod("vp", oldClass: self, oldSelector: "layoutSubviews", newClass: self)
|
vp_swizzled_instanceMethod("vp_bt", oldClass: self, oldSelector: "layoutSubviews", newClass: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func vp_layoutSubviews() {
|
@objc func vp_bt_layoutSubviews() {
|
||||||
vp_layoutSubviews()
|
vp_bt_layoutSubviews()
|
||||||
vp_gradientLayer?.frame = self.bounds
|
bt_gradientLayer?.frame = self.bounds
|
||||||
|
|
||||||
vp_gradientBorder?.frame = self.bounds
|
bt_gradientBorder?.frame = self.bounds
|
||||||
let path = UIBezierPath(roundedRect: bounds.insetBy(dx: 2, dy: 2), cornerRadius: layer.cornerRadius)
|
let path = UIBezierPath(roundedRect: bounds.insetBy(dx: 0.5, dy: 0.5), cornerRadius: layer.cornerRadius)
|
||||||
vp_gradientBorderShapeLayer?.path = path.cgPath
|
bt_gradientBorderShapeLayer?.path = path.cgPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -33,61 +33,61 @@ extension UIButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension UIButton {
|
extension UIButton {
|
||||||
private var vp_gradientLayer: CAGradientLayer? {
|
private var bt_gradientLayer: CAGradientLayer? {
|
||||||
get {
|
get {
|
||||||
return objc_getAssociatedObject(self, &AssociatedKeys.vp_gradientLayer) as? CAGradientLayer
|
return objc_getAssociatedObject(self, &AssociatedKeys.bt_gradientLayer) as? CAGradientLayer
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
objc_setAssociatedObject(self, &AssociatedKeys.vp_gradientLayer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
objc_setAssociatedObject(self, &AssociatedKeys.bt_gradientLayer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var vp_gradientBorder: CAGradientLayer? {
|
private var bt_gradientBorder: CAGradientLayer? {
|
||||||
get {
|
get {
|
||||||
return objc_getAssociatedObject(self, &AssociatedKeys.vp_gradientBorder) as? CAGradientLayer
|
return objc_getAssociatedObject(self, &AssociatedKeys.bt_gradientBorder) as? CAGradientLayer
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
objc_setAssociatedObject(self, &AssociatedKeys.vp_gradientBorder, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
objc_setAssociatedObject(self, &AssociatedKeys.bt_gradientBorder, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var vp_gradientBorderShapeLayer: CAShapeLayer? {
|
private var bt_gradientBorderShapeLayer: CAShapeLayer? {
|
||||||
get {
|
get {
|
||||||
return objc_getAssociatedObject(self, &AssociatedKeys.vp_gradientBorderShapeLayer) as? CAShapeLayer
|
return objc_getAssociatedObject(self, &AssociatedKeys.bt_gradientBorderShapeLayer) as? CAShapeLayer
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
objc_setAssociatedObject(self, &AssociatedKeys.vp_gradientBorderShapeLayer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
objc_setAssociatedObject(self, &AssociatedKeys.bt_gradientBorderShapeLayer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///设置渐变色
|
///设置渐变色
|
||||||
func vp_setGradient() {
|
func bt_setGradient() {
|
||||||
if vp_gradientLayer == nil {
|
if bt_gradientLayer == nil {
|
||||||
let gLayer = CAGradientLayer()
|
let gLayer = CAGradientLayer()
|
||||||
gLayer.colors = [UIColor.color05CEA0().cgColor, UIColor.color7C174F().cgColor]
|
gLayer.colors = [UIColor.color05CEA0().cgColor, UIColor.color7C174F().cgColor]
|
||||||
gLayer.locations = [0, 0.8]
|
gLayer.locations = [0, 0.8]
|
||||||
gLayer.startPoint = .init(x: 0, y: 0.3)
|
gLayer.startPoint = .init(x: 0, y: 0.3)
|
||||||
gLayer.endPoint = .init(x: 1, y: 0.8)
|
gLayer.endPoint = .init(x: 1, y: 0.8)
|
||||||
vp_gradientLayer = gLayer
|
bt_gradientLayer = gLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
if let layer = vp_gradientLayer {
|
if let layer = bt_gradientLayer {
|
||||||
self.layer.addSublayer(layer)
|
self.layer.addSublayer(layer)
|
||||||
self.titleLabel?.layer.zPosition = 1
|
self.titleLabel?.layer.zPosition = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///删除渐变层
|
///删除渐变层
|
||||||
func vp_removeGradient() {
|
func bt_removeGradient() {
|
||||||
vp_gradientLayer?.removeFromSuperlayer()
|
bt_gradientLayer?.removeFromSuperlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///设置渐变边框
|
///设置渐变边框
|
||||||
func vp_setGradientBorder() {
|
func bt_setGradientBorder() {
|
||||||
if vp_gradientBorder == nil {
|
if bt_gradientBorder == nil {
|
||||||
let gLayer = CAGradientLayer()
|
let gLayer = CAGradientLayer()
|
||||||
gLayer.colors = [UIColor.color05CEA0().cgColor, UIColor.color7C174F().cgColor]
|
gLayer.colors = [UIColor.color05CEA0().cgColor, UIColor.color7C174F().cgColor]
|
||||||
gLayer.locations = [0, 0.8]
|
gLayer.locations = [0, 0.8]
|
||||||
@ -100,18 +100,18 @@ extension UIButton {
|
|||||||
shapeLayer.strokeColor = UIColor.black.cgColor
|
shapeLayer.strokeColor = UIColor.black.cgColor
|
||||||
gLayer.mask = shapeLayer
|
gLayer.mask = shapeLayer
|
||||||
|
|
||||||
vp_gradientBorderShapeLayer = shapeLayer
|
bt_gradientBorderShapeLayer = shapeLayer
|
||||||
vp_gradientBorder = gLayer
|
bt_gradientBorder = gLayer
|
||||||
}
|
}
|
||||||
if let layer = vp_gradientBorder {
|
if let layer = bt_gradientBorder {
|
||||||
self.layer.addSublayer(layer)
|
self.layer.addSublayer(layer)
|
||||||
self.titleLabel?.layer.zPosition = 1
|
self.titleLabel?.layer.zPosition = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///删除渐变边框
|
///删除渐变边框
|
||||||
func vp_removeGradientBorder() {
|
func bt_removeGradientBorder() {
|
||||||
vp_gradientBorder?.removeFromSuperlayer()
|
bt_gradientBorder?.removeFromSuperlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,4 +49,24 @@ extension UIColor {
|
|||||||
static func colorFFFFFB(alpha: CGFloat = 1) -> UIColor {
|
static func colorFFFFFB(alpha: CGFloat = 1) -> UIColor {
|
||||||
return UIColor(rgb: 0xFFFFFB, alpha: alpha)
|
return UIColor(rgb: 0xFFFFFB, alpha: alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func color949494(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return UIColor(rgb: 0x949494, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func colorBEBEBE(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return UIColor(rgb: 0xBEBEBE, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func colorFFBD36(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return UIColor(rgb: 0xFFBD36, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func color545458(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return UIColor(rgb: 0x545458, alpha: alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func color1C2D2F(alpha: CGFloat = 1) -> UIColor {
|
||||||
|
return UIColor(rgb: 0x1C2D2F, alpha: alpha)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,110 @@ import SnapKit
|
|||||||
|
|
||||||
extension UIView {
|
extension UIView {
|
||||||
|
|
||||||
|
fileprivate struct AssociatedKeys {
|
||||||
|
static var vp_effect: Int?
|
||||||
|
static var vp_circulars: Int?
|
||||||
|
static var vp_gradientBorder: Int?
|
||||||
|
static var vp_gradientBorderShapeLayer: Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@objc public static func vp_Awake() {
|
||||||
|
vp_swizzled_instanceMethod("vp", oldClass: self, oldSelector: "layoutSubviews", newClass: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func vp_layoutSubviews() {
|
||||||
|
vp_layoutSubviews()
|
||||||
|
|
||||||
|
if let effectView = effectView, effectView.frame != self.bounds {
|
||||||
|
effectView.frame = self.bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
if let border = vp_gradientBorder {
|
||||||
|
border.frame = self.bounds
|
||||||
|
let path = UIBezierPath(roundedRect: bounds.insetBy(dx: 0.5, dy: 0.5), cornerRadius: layer.cornerRadius)
|
||||||
|
vp_gradientBorderShapeLayer?.path = path.cgPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//MARK: -------------- 模糊效果 --------------
|
||||||
|
extension UIView {
|
||||||
|
private var effectView: UIVisualEffectView? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &AssociatedKeys.vp_effect) as? UIVisualEffectView
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.vp_effect, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///添加模糊效果
|
||||||
|
func addEffectView(style: UIBlurEffect.Style = .dark) {
|
||||||
|
if self.effectView == nil {
|
||||||
|
let blur = UIBlurEffect(style: style)
|
||||||
|
let effectView = UIVisualEffectView(effect: blur)
|
||||||
|
effectView.isUserInteractionEnabled = false
|
||||||
|
self.addSubview(effectView)
|
||||||
|
self.sendSubviewToBack(effectView)
|
||||||
|
|
||||||
|
self.effectView = effectView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
///删除模糊效果
|
||||||
|
func removeEffectView() {
|
||||||
|
self.effectView?.removeFromSuperview()
|
||||||
|
self.effectView = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- 渐变边框 --------------
|
||||||
|
extension UIView {
|
||||||
|
private var vp_gradientBorder: CAGradientLayer? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &AssociatedKeys.vp_gradientBorder) as? CAGradientLayer
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.vp_gradientBorder, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var vp_gradientBorderShapeLayer: CAShapeLayer? {
|
||||||
|
get {
|
||||||
|
return objc_getAssociatedObject(self, &AssociatedKeys.vp_gradientBorderShapeLayer) as? CAShapeLayer
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
objc_setAssociatedObject(self, &AssociatedKeys.vp_gradientBorderShapeLayer, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///设置渐变边框
|
||||||
|
func vp_setGradientBorder() {
|
||||||
|
if vp_gradientBorder == nil {
|
||||||
|
let gLayer = CAGradientLayer()
|
||||||
|
gLayer.colors = [UIColor.color05CEA0().cgColor, UIColor.color7C174F().cgColor]
|
||||||
|
gLayer.locations = [0, 0.8]
|
||||||
|
gLayer.startPoint = .init(x: 0, y: 0.3)
|
||||||
|
gLayer.endPoint = .init(x: 1, y: 0.8)
|
||||||
|
|
||||||
|
let shapeLayer = CAShapeLayer()
|
||||||
|
shapeLayer.lineWidth = 1
|
||||||
|
shapeLayer.fillColor = UIColor.clear.cgColor
|
||||||
|
shapeLayer.strokeColor = UIColor.black.cgColor
|
||||||
|
gLayer.mask = shapeLayer
|
||||||
|
|
||||||
|
vp_gradientBorderShapeLayer = shapeLayer
|
||||||
|
vp_gradientBorder = gLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
if let layer = vp_gradientBorder {
|
||||||
|
self.layer.addSublayer(layer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///删除渐变边框
|
||||||
|
func vp_removeGradientBorder() {
|
||||||
|
vp_gradientBorder?.removeFromSuperlayer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,26 @@ import UIKit
|
|||||||
|
|
||||||
class VPVideoAPI: NSObject {
|
class VPVideoAPI: NSObject {
|
||||||
|
|
||||||
|
///获取视频详情
|
||||||
|
static func requestVideoDetail(shortPlayId: String, activityId: String? = nil, completer: ((_ model: VPVideoDetailModel?) -> Void)?) {
|
||||||
|
var parameters: [String : Any] = [
|
||||||
|
"short_play_id" : shortPlayId,
|
||||||
|
"video_id" : "0"
|
||||||
|
]
|
||||||
|
|
||||||
|
if let activityId = activityId {
|
||||||
|
parameters["activity_id"] = activityId
|
||||||
|
}
|
||||||
|
|
||||||
|
var param = VPNetworkParameters(path: "/getVideoDetails")
|
||||||
|
param.method = .get
|
||||||
|
param.parameters = parameters
|
||||||
|
|
||||||
|
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPVideoDetailModel>) in
|
||||||
|
completer?(response.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
///获取分类短剧
|
///获取分类短剧
|
||||||
static func requestCategoryVideoList(id: String, page: Int, completer: ((_ listModel: VPListModel<VPShortModel>?) -> Void)?) {
|
static func requestCategoryVideoList(id: String, page: Int, completer: ((_ listModel: VPListModel<VPShortModel>?) -> Void)?) {
|
||||||
@ -26,4 +46,58 @@ class VPVideoAPI: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///推荐短剧
|
||||||
|
static func requestRecommandsVideo(page: Int, completer: ((_ listModel: VPListModel<VPShortModel>?) -> Void)?) {
|
||||||
|
|
||||||
|
var param = VPNetworkParameters(path: "/getRecommands")
|
||||||
|
param.method = .get
|
||||||
|
param.parameters = [
|
||||||
|
"page_size" : 20,
|
||||||
|
"current_page" : page
|
||||||
|
]
|
||||||
|
|
||||||
|
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPListModel<VPShortModel>>) in
|
||||||
|
completer?(response.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///收藏短剧
|
||||||
|
static func requestCollectShort(isCollect: Bool, shortPlayId: String, videoId: String?, success: (() -> Void)?, failure: (() -> Void)? = nil) {
|
||||||
|
let path: String
|
||||||
|
if isCollect {
|
||||||
|
path = "/collect"
|
||||||
|
} else {
|
||||||
|
path = "/cancelCollect"
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameters: [String : Any] = [
|
||||||
|
"short_play_id" : shortPlayId,
|
||||||
|
]
|
||||||
|
|
||||||
|
if let videoId = videoId {
|
||||||
|
parameters["video_id"] = videoId
|
||||||
|
}
|
||||||
|
|
||||||
|
var param = VPNetworkParameters(path: path)
|
||||||
|
param.isLoding = true
|
||||||
|
param.parameters = parameters
|
||||||
|
|
||||||
|
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<String>) in
|
||||||
|
if response.code == VPNetworkCodeSucceed {
|
||||||
|
success?()
|
||||||
|
NotificationCenter.default.post(name: VPVideoAPI.updateShortCollectStateNotification, object: nil, userInfo: [
|
||||||
|
"state" : isCollect,
|
||||||
|
"id" : shortPlayId,
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
failure?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPVideoAPI {
|
||||||
|
///更新短剧关注状态 [ "state" : isCollect, "id" : shortPlayId,]
|
||||||
|
@objc static let updateShortCollectStateNotification = NSNotification.Name(rawValue: "VPVideoAPI.updateShortCollectStateNotification")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -128,6 +128,8 @@ extension VPTabBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reload() {
|
func reload() {
|
||||||
|
removeAll()
|
||||||
|
|
||||||
guard let items = self.items else { return }
|
guard let items = self.items else { return }
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,8 +50,8 @@ class VPTabBarItemSelectedView: VPGradientView {
|
|||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
colors = [UIColor.color7C174F().cgColor, UIColor.color05CEA0().cgColor]
|
colors = [UIColor.color7C174F().cgColor, UIColor.color05CEA0().cgColor]
|
||||||
locations = [0, 1]
|
locations = [0, 1]
|
||||||
startPoint = .init(x: 0, y: 0.5)
|
startPoint = .init(x: 0, y: 0.3)
|
||||||
endPoint = .init(x: 1, y: 0.5)
|
endPoint = .init(x: 1, y: 0.8)
|
||||||
|
|
||||||
layer.cornerRadius = VPTabBar.itemMinWidth / 2
|
layer.cornerRadius = VPTabBar.itemMinWidth / 2
|
||||||
layer.masksToBounds = true
|
layer.masksToBounds = true
|
||||||
|
21
Veloria/Base/View/VPScrollView.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// VPScrollView.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPScrollView: UIScrollView {
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.contentInsetAdjustmentBehavior = .never
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// VPExploreViewController.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPExploreViewController: VPVideoPlayerViewController {
|
||||||
|
|
||||||
|
override var PlayerCellClass: VPVideoPlayerCell.Type {
|
||||||
|
return VPExplorePlayerCell.self
|
||||||
|
}
|
||||||
|
|
||||||
|
var pagination: VPListPaginationModel?
|
||||||
|
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
self.delegate = self
|
||||||
|
self.dataSource = self
|
||||||
|
|
||||||
|
|
||||||
|
requestDataArr(page: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
self.navigationController?.setNavigationBarHidden(true, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- VPPlayerListViewControllerDelegate --------------
|
||||||
|
extension VPExploreViewController: VPPlayerListViewControllerDelegate {
|
||||||
|
func vp_playerViewControllerLoadMoreData(playerViewController: VPVideoPlayerViewController) {
|
||||||
|
guard let pagination = self.pagination else { return }
|
||||||
|
guard let page = self.pagination?.current_page else { return }
|
||||||
|
let pageSize = pagination.page_size ?? 0
|
||||||
|
if pagination.page_total ?? 0 <= pageSize * page {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.requestDataArr(page: page + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- VPPlayerListViewControllerDataSource --------------
|
||||||
|
extension VPExploreViewController: VPPlayerListViewControllerDataSource {
|
||||||
|
|
||||||
|
func vp_playerListViewController(_ viewController: VPVideoPlayerViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell {
|
||||||
|
|
||||||
|
if let cell = oldCell as? VPVideoPlayerCell {
|
||||||
|
if let model = dataArr[indexPath.row] as? VPShortModel {
|
||||||
|
cell.shortModel = model
|
||||||
|
cell.videoInfo = model.video_info
|
||||||
|
}
|
||||||
|
cell.isLoop = false
|
||||||
|
}
|
||||||
|
return oldCell
|
||||||
|
}
|
||||||
|
|
||||||
|
func vp_playerListViewController(_ viewController: VPVideoPlayerViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int {
|
||||||
|
return oldNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPExploreViewController {
|
||||||
|
|
||||||
|
private func requestDataArr(page: Int) {
|
||||||
|
|
||||||
|
VPVideoAPI.requestRecommandsVideo(page: page) { [weak self] listModel in
|
||||||
|
guard let self = self else { return }
|
||||||
|
if let listModel = listModel, let list = listModel.list {
|
||||||
|
if page == 1 {
|
||||||
|
self.setDataArr(dataArr: list) { [weak self] in
|
||||||
|
self?.play()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.addDataArr(dataArr: list)
|
||||||
|
}
|
||||||
|
self.pagination = listModel.pagination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
Veloria/Class/Explore/View/VPExplorePlayerCell.swift
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// VPExplorePlayerCell.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPExplorePlayerCell: VPVideoPlayerCell {
|
||||||
|
|
||||||
|
override var ControlViewClass: VPVideoPlayerControlView.Type {
|
||||||
|
return VPExplorePlayerControlView.self
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
131
Veloria/Class/Explore/View/VPExplorePlayerControlView.swift
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
//
|
||||||
|
// VPExplorePlayerControlView.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPExplorePlayerControlView: VPVideoPlayerControlView {
|
||||||
|
|
||||||
|
override var videoInfo: VPVideoInfoModel? {
|
||||||
|
didSet {
|
||||||
|
epLabel.text = String(format: "EP.%@".localized, videoInfo?.episode ?? "0")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var shortModel: VPShortModel? {
|
||||||
|
didSet {
|
||||||
|
allView.setTitle(String(format: "All %@ Episodes".localized, "\(shortModel?.episode_total ?? 0)"), for: .normal)
|
||||||
|
videoNameLabel.text = shortModel?.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private lazy var moreButton: UIControl = {
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.backgroundColor = .color000000(alpha: 0.2)
|
||||||
|
button.addTarget(self, action: #selector(handleMoreButton), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var epIconImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "ep_icon_01"))
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var epLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontRegular(ofSize: 12)
|
||||||
|
label.textColor = .colorFFFFFF()
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var allView: UIButton = {
|
||||||
|
let view = JXButton(type: .custom)
|
||||||
|
view.isUserInteractionEnabled = false
|
||||||
|
view.jx_font = .fontRegular(ofSize: 12)
|
||||||
|
view.titleDirection = .left
|
||||||
|
view.space = 6
|
||||||
|
view.setTitleColor(.colorFFFFFB(), for: .normal)
|
||||||
|
view.setImage(UIImage(named: "arrow_right_icon_01"), for: .normal)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var videoNameLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontMedium(ofSize: 14)
|
||||||
|
label.textColor = .colorFFFFFF()
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
vp_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPExplorePlayerControlView {
|
||||||
|
|
||||||
|
@objc private func handleMoreButton() {
|
||||||
|
let vc = VPDetailPlayerViewController()
|
||||||
|
vc.shortPlayId = self.shortModel?.short_play_id
|
||||||
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPExplorePlayerControlView {
|
||||||
|
|
||||||
|
private func vp_setupUI() {
|
||||||
|
self.progressView.insets = .init(top: 30, left: 15, bottom: 6, right: 15)
|
||||||
|
|
||||||
|
addSubview(videoNameLabel)
|
||||||
|
self.progressView.addSubview(moreButton)
|
||||||
|
moreButton.addSubview(epIconImageView)
|
||||||
|
moreButton.addSubview(epLabel)
|
||||||
|
moreButton.addSubview(allView)
|
||||||
|
|
||||||
|
self.progressView.snp.remakeConstraints { make in
|
||||||
|
make.left.equalToSuperview()
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
videoNameLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.bottom.equalTo(self.progressView.snp.top).offset(-15)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-120)
|
||||||
|
}
|
||||||
|
|
||||||
|
moreButton.snp.makeConstraints { make in
|
||||||
|
make.left.right.top.equalToSuperview()
|
||||||
|
make.height.equalTo(30)
|
||||||
|
}
|
||||||
|
|
||||||
|
epIconImageView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
}
|
||||||
|
|
||||||
|
epLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.left.equalTo(epIconImageView.snp.right).offset(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
allView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -32,7 +32,7 @@ class VPHomeListViewController: VPViewController, WMZPageProtocol {
|
|||||||
collectionView.delegate = self
|
collectionView.delegate = self
|
||||||
collectionView.dataSource = self
|
collectionView.dataSource = self
|
||||||
collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.customTabBarHeight + 10, right: 0)
|
collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.customTabBarHeight + 10, right: 0)
|
||||||
collectionView.vp_addRefreshBackFooter(insetBottom: collectionView.contentInset.bottom) { [weak self] in
|
collectionView.vp_addRefreshBackFooter(insetBottom: 0) { [weak self] in
|
||||||
self?.handleFooterRefresh(nil)
|
self?.handleFooterRefresh(nil)
|
||||||
}
|
}
|
||||||
collectionView.register(VPHomeListCell.self, forCellWithReuseIdentifier: "cell")
|
collectionView.register(VPHomeListCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
@ -93,6 +93,13 @@ extension VPHomeListViewController: UICollectionViewDelegate, UICollectionViewDa
|
|||||||
return self.dataArr.count
|
return self.dataArr.count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
let model = dataArr[indexPath.row]
|
||||||
|
let vc = VPDetailPlayerViewController()
|
||||||
|
vc.shortPlayId = model.short_play_id
|
||||||
|
self.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VPHomeListViewController {
|
extension VPHomeListViewController {
|
||||||
|
@ -149,11 +149,11 @@ class VPHomePageViewController: VPViewController {
|
|||||||
private func setButtonState(button: UIButton) {
|
private func setButtonState(button: UIButton) {
|
||||||
button.layer.masksToBounds = true
|
button.layer.masksToBounds = true
|
||||||
if button.isSelected {
|
if button.isSelected {
|
||||||
button.vp_setGradient()
|
button.bt_setGradient()
|
||||||
button.vp_removeGradientBorder()
|
button.bt_removeGradientBorder()
|
||||||
} else {
|
} else {
|
||||||
button.vp_removeGradient()
|
button.bt_removeGradient()
|
||||||
button.vp_setGradientBorder()
|
button.bt_setGradientBorder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,4 +71,12 @@ extension VPHomeBannerContentCell: ZKCycleScrollViewDataSource, ZKCycleScrollVie
|
|||||||
cell.model = self.item?.list?[index]
|
cell.model = self.item?.list?[index]
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didSelectItemAt index: Int) {
|
||||||
|
guard let model = self.item?.list?[index] else { return }
|
||||||
|
|
||||||
|
let vc = VPDetailPlayerViewController()
|
||||||
|
vc.shortPlayId = model.short_play_id
|
||||||
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,4 +105,10 @@ extension VPHomeRankingContentCell: UICollectionViewDelegate, UICollectionViewDa
|
|||||||
return item?.list?.count ?? 0
|
return item?.list?.count ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
guard let model = item?.list?[indexPath.row] else { return }
|
||||||
|
let vc = VPDetailPlayerViewController()
|
||||||
|
vc.shortPlayId = model.short_play_id
|
||||||
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,4 +97,11 @@ extension VPHomeRecommandContentCell: UICollectionViewDelegate, UICollectionView
|
|||||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
return self.item?.list?.count ?? 0
|
return self.item?.list?.count ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
guard let model = item?.list?[indexPath.row] else { return }
|
||||||
|
let vc = VPDetailPlayerViewController()
|
||||||
|
vc.shortPlayId = model.short_play_id
|
||||||
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,166 @@
|
|||||||
|
//
|
||||||
|
// VPDetailPlayerViewController.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPDetailPlayerViewController: VPVideoPlayerViewController {
|
||||||
|
|
||||||
|
override var PlayerCellClass: VPVideoPlayerCell.Type {
|
||||||
|
return VPDetailPlayerCell.self
|
||||||
|
}
|
||||||
|
|
||||||
|
override var contentSize: CGSize {
|
||||||
|
return .init(width: UIScreen.width, height: UIScreen.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortPlayId: String?
|
||||||
|
var activityId: String?
|
||||||
|
|
||||||
|
private var detailModel: VPVideoDetailModel?
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private lazy var backButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.setImage(UIImage(named: "arrow_left_icon_01"), for: .normal)
|
||||||
|
button.addTarget(self, action: #selector(handleBack), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var videoNameLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontMedium(ofSize: 16)
|
||||||
|
label.textColor = .colorFFFFFF()
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
///选集
|
||||||
|
private weak var episodeView: VPEpisodeView?
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
self.delegate = self
|
||||||
|
self.dataSource = self
|
||||||
|
requestDetailData()
|
||||||
|
|
||||||
|
vp_setupUI()
|
||||||
|
vp_addAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
self.navigationController?.setNavigationBarHidden(true, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPDetailPlayerViewController {
|
||||||
|
|
||||||
|
private func vp_setupUI() {
|
||||||
|
view.addSubview(backButton)
|
||||||
|
view.addSubview(videoNameLabel)
|
||||||
|
|
||||||
|
backButton.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(UIScreen.statusBarHeight)
|
||||||
|
make.width.equalTo(48)
|
||||||
|
make.height.equalTo(44)
|
||||||
|
}
|
||||||
|
|
||||||
|
videoNameLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalTo(backButton)
|
||||||
|
make.left.equalTo(backButton.snp.right)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-150)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func vp_addAction() {
|
||||||
|
self.viewModel.handleEpisode = { [weak self] in
|
||||||
|
self?.onEpisode()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPDetailPlayerViewController {
|
||||||
|
|
||||||
|
private func onEpisode() {
|
||||||
|
let view = VPEpisodeView()
|
||||||
|
view.dataArr = detailModel?.episodeList ?? []
|
||||||
|
view.shortModel = detailModel?.shortPlayInfo
|
||||||
|
view.currentIndex = self.currentIndexPath.row
|
||||||
|
view.didSelectedIndex = { [weak self] (index) in
|
||||||
|
self?.scrollToItem(indexPath: IndexPath(row: index, section: 0), animated: false)
|
||||||
|
}
|
||||||
|
view.present(in: nil)
|
||||||
|
self.episodeView = view
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- VPPlayerListViewControllerDataSource --------------
|
||||||
|
extension VPDetailPlayerViewController: VPPlayerListViewControllerDataSource {
|
||||||
|
func vp_playerListViewController(_ viewController: VPVideoPlayerViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell {
|
||||||
|
guard let cell = oldCell as? VPDetailPlayerCell else { return oldCell }
|
||||||
|
cell.shortModel = detailModel?.shortPlayInfo
|
||||||
|
cell.videoInfo = detailModel?.episodeList?[indexPath.row]
|
||||||
|
cell.isLoop = false
|
||||||
|
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func vp_playerListViewController(_ viewController: VPVideoPlayerViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int {
|
||||||
|
return self.detailModel?.episodeList?.count ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- VPPlayerListViewControllerDelegate --------------
|
||||||
|
extension VPDetailPlayerViewController: VPPlayerListViewControllerDelegate {
|
||||||
|
func vp_playerListViewController(_ viewController: VPVideoPlayerViewController, didChangeIndexPathForVisible indexPath: IndexPath) {
|
||||||
|
self.episodeView?.currentIndex = indexPath.row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
extension VPDetailPlayerViewController {
|
||||||
|
|
||||||
|
private func requestDetailData() {
|
||||||
|
guard let shortPlayId = shortPlayId else { return }
|
||||||
|
|
||||||
|
VPVideoAPI.requestVideoDetail(shortPlayId: shortPlayId, activityId: activityId) { [weak self] model in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard let model = model else { return }
|
||||||
|
self.detailModel = model
|
||||||
|
self.videoNameLabel.text = model.shortPlayInfo?.name
|
||||||
|
|
||||||
|
self.reloadData { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
if let videoInfo = self.detailModel?.video_info {
|
||||||
|
var row: Int?
|
||||||
|
self.detailModel?.episodeList?.enumerated().forEach({
|
||||||
|
if $1.id == videoInfo.id {
|
||||||
|
row = $0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if let row = row {
|
||||||
|
self.scrollToItem(indexPath: .init(row: row, section: 0), animated: false)
|
||||||
|
} else {
|
||||||
|
self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,382 @@
|
|||||||
|
//
|
||||||
|
// VPVideoPlayerViewController.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by Veloria on 2025/5/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@objc protocol VPPlayerListViewControllerDelegate {
|
||||||
|
|
||||||
|
///加载新数据
|
||||||
|
@objc optional func vp_playerViewControllerLoadNewDataV2(playerViewController: VPVideoPlayerViewController)
|
||||||
|
|
||||||
|
///将要加载更多数据
|
||||||
|
@objc optional func vp_playerViewControllerShouldLoadMoreData(playerViewController: VPVideoPlayerViewController) -> Bool
|
||||||
|
///加载更多数据
|
||||||
|
@objc optional func vp_playerViewControllerLoadMoreData(playerViewController: VPVideoPlayerViewController)
|
||||||
|
///向上加载更多数据
|
||||||
|
@objc optional func vp_playerViewControllerLoadUpMoreData(playerViewController: VPVideoPlayerViewController)
|
||||||
|
|
||||||
|
///当前展示的发生变化
|
||||||
|
@objc optional func vp_playerListViewController(_ viewController: VPVideoPlayerViewController, didChangeIndexPathForVisible indexPath: IndexPath)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc protocol VPPlayerListViewControllerDataSource {
|
||||||
|
|
||||||
|
|
||||||
|
func vp_playerListViewController(_ viewController: VPVideoPlayerViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell
|
||||||
|
|
||||||
|
func vp_playerListViewController(_ viewController: VPVideoPlayerViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class VPVideoPlayerViewController: VPViewController {
|
||||||
|
|
||||||
|
var contentSize: CGSize {
|
||||||
|
return CGSize(width: UIScreen.width, height: UIScreen.height - UIScreen.customTabBarHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
var PlayerCellClass: VPVideoPlayerCell.Type {
|
||||||
|
return VPVideoPlayerCell.self
|
||||||
|
}
|
||||||
|
|
||||||
|
weak var delegate: VPPlayerListViewControllerDelegate?
|
||||||
|
weak var dataSource: VPPlayerListViewControllerDataSource?
|
||||||
|
|
||||||
|
private(set) var viewModel = VPVideoPlayViewModel()
|
||||||
|
|
||||||
|
private(set) var dataArr: [Any] = []
|
||||||
|
|
||||||
|
private(set) var currentIndexPath = IndexPath(row: 0, section: 0)
|
||||||
|
|
||||||
|
///自动下一级
|
||||||
|
var autoNextEpisode = true
|
||||||
|
|
||||||
|
private lazy var collectionViewLayout: UICollectionViewLayout = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.itemSize = contentSize
|
||||||
|
layout.minimumInteritemSpacing = 0
|
||||||
|
layout.minimumLineSpacing = 0
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
private(set) lazy var collectionView: VPCollectionView = {
|
||||||
|
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.isPagingEnabled = true
|
||||||
|
collectionView.showsVerticalScrollIndicator = false
|
||||||
|
collectionView.showsHorizontalScrollIndicator = false
|
||||||
|
collectionView.bounces = false
|
||||||
|
collectionView.scrollsToTop = false
|
||||||
|
// PlayerCellClass.registerCell(collectionView: collectionView)
|
||||||
|
collectionView.register(PlayerCellClass.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActiveNotification), name: UIApplication.didBecomeActiveNotification, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(willResignActiveNotification), name: UIApplication.willResignActiveNotification, object: nil)
|
||||||
|
|
||||||
|
do {
|
||||||
|
try? KTVHTTPCache.proxyStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
vp_setupUI()
|
||||||
|
vp_addActio()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
if getDataCount() > 0 && self.viewModel.isPlaying {
|
||||||
|
self.viewModel.currentPlayer?.start()
|
||||||
|
// self.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
self.viewModel.currentPlayer?.pause()
|
||||||
|
///处理切换页面后,滑动代理scrollViewDidEndDecelerating不执行的问题
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.scrollDidEnd(self.collectionView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func play() {
|
||||||
|
if self.isDidAppear {
|
||||||
|
self.viewModel.currentPlayer?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.viewModel.isPlaying = true
|
||||||
|
|
||||||
|
if getDataCount() - currentIndexPath.row <= 2 {
|
||||||
|
self.loadMoreData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// if isFirstPlay {
|
||||||
|
// isFirstPlay = false
|
||||||
|
// let offset = self.collectionView.contentOffset.y + 0.2
|
||||||
|
// self.collectionView.setContentOffset(CGPoint(x: 0, y: offset), animated: false)
|
||||||
|
//
|
||||||
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
// let offset = self.collectionView.contentOffset.y
|
||||||
|
// self.collectionView.setContentOffset(CGPoint(x: 0, y: floor(offset)), animated: false)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func pause() {
|
||||||
|
self.viewModel.isPlaying = false
|
||||||
|
self.viewModel.currentPlayer?.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDataArr(dataArr: [Any], completer: (() -> Void)?) {
|
||||||
|
self.dataArr = dataArr
|
||||||
|
reloadData(completion: completer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDataArr(dataArr: [Any]) {
|
||||||
|
guard dataArr.count > 0 else { return }
|
||||||
|
|
||||||
|
var indexPaths: [IndexPath] = []
|
||||||
|
var startRow = self.dataArr.count
|
||||||
|
|
||||||
|
dataArr.forEach { _ in
|
||||||
|
indexPaths.append(IndexPath(row: startRow, section: 0))
|
||||||
|
startRow += 1
|
||||||
|
}
|
||||||
|
self.dataArr += dataArr
|
||||||
|
|
||||||
|
CATransaction.setCompletionBlock(nil)
|
||||||
|
CATransaction.begin()
|
||||||
|
self.collectionView.insertItems(at: indexPaths)
|
||||||
|
CATransaction.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadData(completion: (() -> Void)? = nil) {
|
||||||
|
CATransaction.setCompletionBlock { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let cell = self.collectionView.cellForItem(at: self.currentIndexPath) as? VPVideoPlayerCell
|
||||||
|
self.viewModel.currentPlayer = cell
|
||||||
|
|
||||||
|
completion?()
|
||||||
|
}
|
||||||
|
CATransaction.begin()
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
CATransaction.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollToItem(indexPath: IndexPath, animated: Bool = true, completer: (() -> Void)? = nil) {
|
||||||
|
CATransaction.setCompletionBlock { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
if !animated {
|
||||||
|
if self.currentIndexPath != indexPath {
|
||||||
|
self.skip(indexPath: indexPath)
|
||||||
|
} else {
|
||||||
|
self.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completer?()
|
||||||
|
}
|
||||||
|
CATransaction.begin()
|
||||||
|
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: animated);
|
||||||
|
CATransaction.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
///当前播放完成 子类可重写
|
||||||
|
func currentPlayFinish() {
|
||||||
|
if self.autoNextEpisode {
|
||||||
|
scrollToNextEpisode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///当前播放进度变更 子类可重写
|
||||||
|
func currentPlayTimeDidChange(time: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPVideoPlayerViewController {
|
||||||
|
func getDataCount() -> Int {
|
||||||
|
return self.collectionView(self.collectionView, numberOfItemsInSection: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
///点击播放或暂停
|
||||||
|
private func clickPauseOrPlay() {
|
||||||
|
if self.viewModel.isPlaying {
|
||||||
|
self.pause()
|
||||||
|
} else {
|
||||||
|
self.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///滑动至下一级
|
||||||
|
private func scrollToNextEpisode() {
|
||||||
|
|
||||||
|
var contentOffset = self.collectionView.contentOffset
|
||||||
|
|
||||||
|
if hasNextEpisode() {
|
||||||
|
contentOffset.y = floor(contentOffset.y + self.contentSize.height)
|
||||||
|
self.collectionView.setContentOffset(contentOffset, animated: true)
|
||||||
|
} else {
|
||||||
|
self.viewModel.currentPlayer?.replay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///是否还有下一级
|
||||||
|
private func hasNextEpisode() -> Bool {
|
||||||
|
let contentOffset = self.collectionView.contentOffset
|
||||||
|
let contentSize = self.collectionView.contentSize
|
||||||
|
if contentOffset.y >= contentSize.height - self.contentSize.height {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension VPVideoPlayerViewController {
|
||||||
|
private func vp_setupUI() {
|
||||||
|
view.addSubview(collectionView)
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.left.equalToSuperview()
|
||||||
|
make.width.equalTo(self.contentSize.width)
|
||||||
|
make.height.equalTo(self.contentSize.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func vp_addActio() {
|
||||||
|
self.viewModel.handlePauseOrPlay = { [weak self] in
|
||||||
|
self?.clickPauseOrPlay()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.viewModel.handlePlayFinish = { [weak self] in
|
||||||
|
self?.currentPlayFinish()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.viewModel.handlePlayTimeDidChange = { [weak self] time in
|
||||||
|
self?.currentPlayTimeDidChange(time: time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
|
||||||
|
extension VPVideoPlayerViewController: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
var cell: UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
|
||||||
|
|
||||||
|
if let newCell = self.dataSource?.vp_playerListViewController(self, collectionView, cellForItemAt: indexPath, oldCell: cell) {
|
||||||
|
cell = newCell
|
||||||
|
}
|
||||||
|
|
||||||
|
if let cell = cell as? VPVideoPlayerCell {
|
||||||
|
if cell.viewModel == nil {
|
||||||
|
cell.viewModel = viewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.viewModel.currentPlayer == nil, indexPath == currentIndexPath, let playerProtocol = cell as? VPPlayerProtocol {
|
||||||
|
self.currentIndexPath = indexPath
|
||||||
|
self.viewModel.currentPlayer = playerProtocol
|
||||||
|
didChangeIndexPathForVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
let count = dataArr.count
|
||||||
|
if let newCount = self.dataSource?.vp_playerListViewController(self, collectionView, numberOfItemsInSection: section, oldNumber: count) {
|
||||||
|
return newCount
|
||||||
|
} else {
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//滑动停止
|
||||||
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||||
|
scrollDidEnd(scrollView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||||||
|
scrollDidEnd(scrollView)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func scrollDidEnd(_ scrollView: UIScrollView) {
|
||||||
|
let offsetY = scrollView.contentOffset.y
|
||||||
|
let indexPaths = self.collectionView.indexPathsForVisibleItems
|
||||||
|
for indexPath in indexPaths {
|
||||||
|
guard let cell = self.collectionView.cellForItem(at: indexPath) else { continue }
|
||||||
|
if floor(offsetY) == floor(cell.frame.origin.y) {
|
||||||
|
if self.currentIndexPath != indexPath {
|
||||||
|
self.skip(indexPath: indexPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func skip(indexPath: IndexPath) {
|
||||||
|
currentIndexPath = indexPath
|
||||||
|
guard let currentPlayer = self.collectionView.cellForItem(at: indexPath) as? VPPlayerProtocol else { return }
|
||||||
|
self.viewModel.currentPlayer = currentPlayer
|
||||||
|
// currentCell = self.collectionView.cellForItem(at: indexPath) as? BCListPlayerCell
|
||||||
|
didChangeIndexPathForVisible()
|
||||||
|
self.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension VPVideoPlayerViewController {
|
||||||
|
|
||||||
|
private func loadMoreData() {
|
||||||
|
let isLoad = self.delegate?.vp_playerViewControllerShouldLoadMoreData?(playerViewController: self)
|
||||||
|
if isLoad != false {
|
||||||
|
self.delegate?.vp_playerViewControllerLoadMoreData?(playerViewController: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadUpMoreData() {
|
||||||
|
self.delegate?.vp_playerViewControllerLoadUpMoreData?(playerViewController: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func didChangeIndexPathForVisible() {
|
||||||
|
self.delegate?.vp_playerListViewController?(self, didChangeIndexPathForVisible: self.currentIndexPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- APP生命周期 --------------
|
||||||
|
extension VPVideoPlayerViewController {
|
||||||
|
|
||||||
|
@objc func didBecomeActiveNotification() {
|
||||||
|
if getDataCount() > 0 && self.viewModel.isPlaying && isDidAppear {
|
||||||
|
self.viewModel.currentPlayer?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func willResignActiveNotification() {
|
||||||
|
self.viewModel.currentPlayer?.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
46
Veloria/Class/Player/Model/VPPlayerProtocol.swift
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// VPPlayerProtocol.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by Veloria on 2025/5/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@objc protocol VPPlayerProtocol: NSObjectProtocol {
|
||||||
|
|
||||||
|
///播放完成
|
||||||
|
var playerFinishHadle: (() -> Void)? { get set }
|
||||||
|
|
||||||
|
// var model: Any? { get set }
|
||||||
|
// var videoInfo: VPVideoInfoModel? { get set }
|
||||||
|
|
||||||
|
var isCurrent: Bool { get set }
|
||||||
|
|
||||||
|
///上一集是否加锁
|
||||||
|
@objc optional var hasLockUpEpisode: Bool { get set }
|
||||||
|
|
||||||
|
///总进度
|
||||||
|
var duration: Int { get }
|
||||||
|
///当前进度
|
||||||
|
var currentPosition: Int { get }
|
||||||
|
|
||||||
|
var rate: Float { get set }
|
||||||
|
|
||||||
|
///播放准备
|
||||||
|
func prepare()
|
||||||
|
|
||||||
|
///开始播放
|
||||||
|
func start()
|
||||||
|
|
||||||
|
///暂停播放
|
||||||
|
func pause()
|
||||||
|
|
||||||
|
///从头播放
|
||||||
|
func replay()
|
||||||
|
|
||||||
|
///设置进度
|
||||||
|
func seekToTime(toTime: Int)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
23
Veloria/Class/Player/Model/VPVideoDetailModel.swift
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// VPVideoDetailModel.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SmartCodable
|
||||||
|
|
||||||
|
class VPVideoDetailModel: VPModel, SmartCodable {
|
||||||
|
var business_model: String?
|
||||||
|
var video_info: VPVideoInfoModel?
|
||||||
|
var shortPlayInfo: VPShortModel?
|
||||||
|
var episodeList: [VPVideoInfoModel]?
|
||||||
|
var is_collect: Bool?
|
||||||
|
var show_share_coin: Int?
|
||||||
|
var share_coin: Int?
|
||||||
|
var install_coins: Int?
|
||||||
|
var revolution: Int?
|
||||||
|
var unlock_video_ad_count: Int?
|
||||||
|
var discount: Int?
|
||||||
|
}
|
69
Veloria/Class/Player/Model/VPVideoRateModel.swift
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
//
|
||||||
|
// VPVideoRateModel.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPVideoRateModel: NSObject {
|
||||||
|
|
||||||
|
enum Rate: String {
|
||||||
|
case x0_5 = "0.5x"
|
||||||
|
case x0_75 = "0.75x"
|
||||||
|
case x1 = "1.0x"
|
||||||
|
case x1_25 = "1.25x"
|
||||||
|
case x1_5 = "1.5x"
|
||||||
|
case x1_75 = "1.75x"
|
||||||
|
case x2 = "2.0x"
|
||||||
|
|
||||||
|
func getRate() -> Float {
|
||||||
|
switch self {
|
||||||
|
case .x0_5:
|
||||||
|
return 0.5
|
||||||
|
|
||||||
|
case .x0_75:
|
||||||
|
return 0.75
|
||||||
|
|
||||||
|
case .x1:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
case .x1_25:
|
||||||
|
return 1.25
|
||||||
|
|
||||||
|
case .x1_5:
|
||||||
|
return 1.5
|
||||||
|
|
||||||
|
case .x1_75:
|
||||||
|
return 1.75
|
||||||
|
|
||||||
|
case .x2:
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func getAllRate() -> [VPVideoRateModel] {
|
||||||
|
return [
|
||||||
|
VPVideoRateModel(rate: .x0_75),
|
||||||
|
VPVideoRateModel(rate: .x1),
|
||||||
|
VPVideoRateModel(rate: .x1_25),
|
||||||
|
VPVideoRateModel(rate: .x1_5),
|
||||||
|
VPVideoRateModel(rate: .x1_75),
|
||||||
|
VPVideoRateModel(rate: .x2)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
var rate: Rate = .x1
|
||||||
|
|
||||||
|
init(rate: Rate) {
|
||||||
|
super.init()
|
||||||
|
self.rate = rate
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatString() -> String {
|
||||||
|
return self.rate.rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
Veloria/Class/Player/View/VPDetailPlayerCell.swift
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// VPDetailPlayerCell.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPDetailPlayerCell: VPVideoPlayerCell {
|
||||||
|
|
||||||
|
override var ControlViewClass: VPVideoPlayerControlView.Type {
|
||||||
|
return VPDetailPlayerControlView.self
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
221
Veloria/Class/Player/View/VPDetailPlayerControlView.swift
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
//
|
||||||
|
// VPDetailPlayerControlView.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPDetailPlayerControlView: VPVideoPlayerControlView {
|
||||||
|
|
||||||
|
override var viewModel: VPVideoPlayViewModel? {
|
||||||
|
didSet {
|
||||||
|
self.viewModel?.addObserver(self, forKeyPath: "rateModel", options: .new, context: nil)
|
||||||
|
|
||||||
|
rateButton.setTitle(self.viewModel?.rateModel.formatString(), for: .normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override var videoInfo: VPVideoInfoModel? {
|
||||||
|
didSet {
|
||||||
|
epView.setTitle(String(format: "EP.%@".localized, "\(videoInfo?.episode ?? "0")"), for: .normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var shortModel: VPShortModel? {
|
||||||
|
didSet {
|
||||||
|
allEpView.setTitle(String(format: "All %@ Episodes".localized, "\(shortModel?.episode_total ?? 0)"), for: .normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var durationTime: Int {
|
||||||
|
didSet {
|
||||||
|
updateTimeLabel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var currentTime: Int {
|
||||||
|
didSet {
|
||||||
|
updateTimeLabel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- UI属性 --------------
|
||||||
|
private lazy var bottomView: VPGradientView = {
|
||||||
|
let view = VPGradientView()
|
||||||
|
view.isUserInteractionEnabled = false
|
||||||
|
view.colors = [UIColor.color000000(alpha: 0).cgColor, UIColor.color000000(alpha: 0.5).cgColor, UIColor.color000000(alpha: 1).cgColor]
|
||||||
|
view.locations = [0, 0.5, 1]
|
||||||
|
view.startPoint = .init(x: 0.5, y: 0)
|
||||||
|
view.endPoint = .init(x: 0.5, y: 1)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var epBgView: UIView = {
|
||||||
|
let view = UIButton(type: .custom)
|
||||||
|
view.setBackgroundImage(UIImage(color: .color949494(alpha: 0.4)), for: .normal)
|
||||||
|
view.layer.cornerRadius = 15
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
view.addTarget(self, action: #selector(handleEpisodesButton), for: .touchUpInside)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var epView: UIButton = {
|
||||||
|
let view = JXButton(type: .custom)
|
||||||
|
view.isUserInteractionEnabled = false
|
||||||
|
view.titleDirection = .right
|
||||||
|
view.jx_font = .fontRegular(ofSize: 13)
|
||||||
|
view.space = 5
|
||||||
|
view.setTitleColor(.colorFFFFFF(), for: .normal)
|
||||||
|
view.setImage(UIImage(named: "ep_icon_01"), for: .normal)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var allEpView: UIButton = {
|
||||||
|
let view = JXButton(type: .custom)
|
||||||
|
view.isUserInteractionEnabled = false
|
||||||
|
view.titleDirection = .left
|
||||||
|
view.jx_font = .fontRegular(ofSize: 13)
|
||||||
|
view.space = 4
|
||||||
|
view.setTitleColor(.colorBEBEBE(), for: .normal)
|
||||||
|
view.setImage(UIImage(named: "arrow_up_icon_01"), for: .normal)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var rateButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.setBackgroundImage(UIImage(color: .color949494(alpha: 0.4)), for: .normal)
|
||||||
|
button.layer.cornerRadius = 15
|
||||||
|
button.layer.masksToBounds = true
|
||||||
|
button.setTitleColor(.colorFFFFFF(), for: .normal)
|
||||||
|
button.titleLabel?.font = .fontRegular(ofSize: 13)
|
||||||
|
button.addTarget(self, action: #selector(handleRateButton), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var timeLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontRegular(ofSize: 12)
|
||||||
|
label.textColor = .colorFFFFFB(alpha: 0.9)
|
||||||
|
label.isUserInteractionEnabled = false
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
///倍速选择
|
||||||
|
private lazy var rateSelectedView: VPRateSelectedView = {
|
||||||
|
let view = VPRateSelectedView()
|
||||||
|
view.didSelected = { [weak self] model in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.viewModel?.rateModel = model
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.viewModel?.removeObserver(self, forKeyPath: "rateModel")
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
vp_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
||||||
|
|
||||||
|
if keyPath == "rateModel" {
|
||||||
|
rateButton.setTitle(self.viewModel?.rateModel.formatString(), for: .normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPDetailPlayerControlView {
|
||||||
|
|
||||||
|
private func updateTimeLabel() {
|
||||||
|
let currentTime = self.currentTime.formatTimeGroup()
|
||||||
|
let durationTime = self.durationTime.formatTimeGroup()
|
||||||
|
|
||||||
|
timeLabel.text = "\(currentTime.1):\(currentTime.2)/\(durationTime.1):\(durationTime.2)"
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleEpisodesButton() {
|
||||||
|
self.viewModel?.handleEpisode?()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleRateButton() {
|
||||||
|
addSubview(rateSelectedView)
|
||||||
|
rateSelectedView.currentRateModel = self.viewModel?.rateModel
|
||||||
|
|
||||||
|
rateSelectedView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPDetailPlayerControlView {
|
||||||
|
|
||||||
|
private func vp_setupUI() {
|
||||||
|
progressView.lineWidth = 3
|
||||||
|
progressView.insets = .init(top: 20, left: 15, bottom: 10, right: 15)
|
||||||
|
|
||||||
|
addSubview(bottomView)
|
||||||
|
addSubview(epBgView)
|
||||||
|
epBgView.addSubview(epView)
|
||||||
|
epBgView.addSubview(allEpView)
|
||||||
|
addSubview(rateButton)
|
||||||
|
addSubview(timeLabel)
|
||||||
|
|
||||||
|
self.sendSubviewToBack(self.bottomView)
|
||||||
|
|
||||||
|
progressView.snp.remakeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.bottom.equalTo(epBgView.snp.top)
|
||||||
|
}
|
||||||
|
|
||||||
|
rightToolView.snp.updateConstraints { make in
|
||||||
|
make.bottom.equalToSuperview().offset(-(UIScreen.tabbarSafeBottomMargin + 150))
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomView.snp.makeConstraints { make in
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
make.height.equalTo(UIScreen.tabbarSafeBottomMargin + 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
epBgView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.bottom.equalToSuperview().offset(-(UIScreen.tabbarSafeBottomMargin + 10))
|
||||||
|
make.height.equalTo(30)
|
||||||
|
}
|
||||||
|
|
||||||
|
epView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(10)
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
allEpView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.right.equalToSuperview().offset(-10)
|
||||||
|
}
|
||||||
|
|
||||||
|
rateButton.snp.makeConstraints { make in
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
make.left.equalTo(epBgView.snp.right).offset(10)
|
||||||
|
make.height.top.equalTo(epBgView)
|
||||||
|
make.width.equalTo(50)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.bottom.equalTo(epBgView.snp.top).offset(-16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
78
Veloria/Class/Player/View/VPEpisodeCell.swift
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
//
|
||||||
|
// VPEpisodeCell.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPEpisodeCell: VPCollectionViewCell {
|
||||||
|
|
||||||
|
|
||||||
|
var videoInfoModel: VPVideoInfoModel? {
|
||||||
|
didSet {
|
||||||
|
numLabel.text = videoInfoModel?.episode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var vp_isSelected: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if vp_isSelected {
|
||||||
|
contentView.vp_setGradientBorder()
|
||||||
|
contentView.backgroundColor = .color05CEA0(alpha: 0.1)
|
||||||
|
} else {
|
||||||
|
contentView.vp_removeGradientBorder()
|
||||||
|
contentView.backgroundColor = .colorFFFFFF(alpha: 0.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var bgView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.vp_setGradientBorder()
|
||||||
|
view.layer.cornerRadius = 6
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private lazy var numLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontRegular(ofSize: 14)
|
||||||
|
label.textColor = .colorFFFFFF()
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
vp_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPEpisodeCell {
|
||||||
|
|
||||||
|
private func vp_setupUI() {
|
||||||
|
contentView.layer.cornerRadius = 6
|
||||||
|
contentView.layer.masksToBounds = true
|
||||||
|
|
||||||
|
// contentView.addSubview(bgView)
|
||||||
|
contentView.addSubview(numLabel)
|
||||||
|
|
||||||
|
// bgView.snp.makeConstraints { make in
|
||||||
|
// make.edges.equalToSuperview()
|
||||||
|
// }
|
||||||
|
|
||||||
|
numLabel.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
152
Veloria/Class/Player/View/VPEpisodeMenuView.swift
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
//
|
||||||
|
// VPEpisodeMenuView.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPEpisodeMenuView: UIView {
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
return CGSize(width: UIScreen.width, height: 35)
|
||||||
|
}
|
||||||
|
|
||||||
|
var didSelectedIndex: ((_ index: Int) -> Void)?
|
||||||
|
|
||||||
|
var dataArr: [String] = [] {
|
||||||
|
didSet {
|
||||||
|
self.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedIndex: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
self.buttonArr.forEach {
|
||||||
|
$0.isSelected = $0.tag == selectedIndex
|
||||||
|
}
|
||||||
|
self.progressSlide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private lazy var buttonArr: [UIButton] = []
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private lazy var progressView: VPGradientView = {
|
||||||
|
let view = VPGradientView()
|
||||||
|
view.colors = [UIColor.color05CEA0().cgColor, UIColor.color7C174F().cgColor]
|
||||||
|
view.startPoint = .init(x: 0, y: 0.3)
|
||||||
|
view.endPoint = .init(x: 1, y: 0.8)
|
||||||
|
view.locations = [0, 1]
|
||||||
|
view.layer.cornerRadius = 1
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var scrollView: VPScrollView = {
|
||||||
|
let scrollView = VPScrollView()
|
||||||
|
scrollView.showsVerticalScrollIndicator = false
|
||||||
|
scrollView.showsHorizontalScrollIndicator = false
|
||||||
|
return scrollView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func reloadData() {
|
||||||
|
buttonArr.forEach {
|
||||||
|
$0.removeFromSuperview()
|
||||||
|
}
|
||||||
|
buttonArr.removeAll()
|
||||||
|
|
||||||
|
let count = self.dataArr.count
|
||||||
|
|
||||||
|
var previousButton: UIButton?
|
||||||
|
|
||||||
|
dataArr.enumerated().forEach {
|
||||||
|
let normalStrig = NSMutableAttributedString(string: $1)
|
||||||
|
normalStrig.color = .colorFFFFFF(alpha: 0.6)
|
||||||
|
normalStrig.font = .fontMedium(ofSize: 14)
|
||||||
|
|
||||||
|
let selectedString = NSMutableAttributedString(string: $1)
|
||||||
|
selectedString.color = .colorFFFFFF()
|
||||||
|
selectedString.font = .fontMedium(ofSize: 14)
|
||||||
|
|
||||||
|
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.tag = $0
|
||||||
|
button.setAttributedTitle(normalStrig, for: .normal)
|
||||||
|
button.setAttributedTitle(selectedString, for: .selected)
|
||||||
|
button.setAttributedTitle(selectedString, for: [.selected, .highlighted])
|
||||||
|
button.addTarget(self, action: #selector(handleButton), for: .touchUpInside)
|
||||||
|
button.isSelected = $0 == selectedIndex
|
||||||
|
|
||||||
|
self.scrollView.addSubview(button)
|
||||||
|
self.buttonArr.append(button)
|
||||||
|
|
||||||
|
if previousButton == nil {
|
||||||
|
button.snp.makeConstraints { make in
|
||||||
|
make.top.left.equalToSuperview()
|
||||||
|
make.height.equalTo(35)
|
||||||
|
}
|
||||||
|
} else if let previousButton = previousButton, count - 1 == $0 {
|
||||||
|
button.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.left.equalTo(previousButton.snp.right).offset(27)
|
||||||
|
make.height.equalTo(35)
|
||||||
|
make.right.equalToSuperview()
|
||||||
|
}
|
||||||
|
} else if let previousButton = previousButton {
|
||||||
|
button.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.left.equalTo(previousButton.snp.right).offset(27)
|
||||||
|
make.height.equalTo(35)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousButton = button
|
||||||
|
}
|
||||||
|
|
||||||
|
progressSlide()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func progressSlide() {
|
||||||
|
if self.selectedIndex >= self.buttonArr.count { return }
|
||||||
|
let currentButton = self.buttonArr[self.selectedIndex]
|
||||||
|
|
||||||
|
self.progressView.snp.remakeConstraints { make in
|
||||||
|
make.bottom.width.equalTo(currentButton)
|
||||||
|
make.centerX.equalTo(currentButton)
|
||||||
|
make.height.equalTo(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@objc private func handleButton(sender: UIButton) {
|
||||||
|
self.selectedIndex = sender.tag
|
||||||
|
self.didSelectedIndex?(self.selectedIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPEpisodeMenuView {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
addSubview(scrollView)
|
||||||
|
scrollView.addSubview(progressView)
|
||||||
|
|
||||||
|
scrollView.snp.makeConstraints { make in
|
||||||
|
make.left.right.top.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
333
Veloria/Class/Player/View/VPEpisodeView.swift
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
//
|
||||||
|
// VPEpisodeView.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPEpisodeView: HWPanModalContentView {
|
||||||
|
|
||||||
|
var currentIndex: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortModel: VPShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.vp_setImage(url: shortModel?.image_url)
|
||||||
|
videoNameLabel.text = shortModel?.name
|
||||||
|
if let category = shortModel?.category?.first, !category.isEmpty {
|
||||||
|
tagView.setTitle(category, for: .normal)
|
||||||
|
tagView.isHidden = false
|
||||||
|
} else {
|
||||||
|
tagView.isHidden = true
|
||||||
|
}
|
||||||
|
desLabel.text = shortModel?.sp_description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataArr: [VPVideoInfoModel] = [] {
|
||||||
|
didSet {
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
|
||||||
|
var menuDataArr = [String]()
|
||||||
|
let totalEpisode = dataArr.count
|
||||||
|
var index = 0
|
||||||
|
var remainingEpisodes = totalEpisode
|
||||||
|
|
||||||
|
while remainingEpisodes > 0 {
|
||||||
|
let minIndex = index * 30
|
||||||
|
var maxIndex = minIndex + 29
|
||||||
|
if maxIndex >= dataArr.count {
|
||||||
|
maxIndex = dataArr.count - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
let minEpisode = dataArr[minIndex].episode ?? "0"
|
||||||
|
let maxEpisode = dataArr[maxIndex].episode ?? "0"
|
||||||
|
|
||||||
|
if minEpisode == maxEpisode {
|
||||||
|
menuDataArr.append("\(minEpisode)")
|
||||||
|
} else {
|
||||||
|
menuDataArr.append("\(minEpisode)-\(maxEpisode)")
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingEpisodes -= 30
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
self.menuView.dataArr = menuDataArr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var didSelectedIndex: ((_ index: Int) -> Void)?
|
||||||
|
|
||||||
|
var isDecelerating = false
|
||||||
|
var isDragging = false
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private lazy var bgView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "bg_image_01"))
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var closeButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom)
|
||||||
|
button.setImage(UIImage(named: "close_icon_01"), for: .normal)
|
||||||
|
button.addTarget(self, action: #selector(handleCloseButton), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var coverImageView: VPImageView = {
|
||||||
|
let imageView = VPImageView()
|
||||||
|
imageView.layer.cornerRadius = 6
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var videoNameLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontMedium(ofSize: 15)
|
||||||
|
label.textColor = .colorFFFFFF()
|
||||||
|
label.numberOfLines = 2
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var tagView: JXButton = {
|
||||||
|
let view = JXButton(type: .custom)
|
||||||
|
view.isUserInteractionEnabled = false
|
||||||
|
view.backgroundColor = .colorFFFFFF(alpha: 0.1)
|
||||||
|
view.leftAndRightMargin = 6
|
||||||
|
view.layer.cornerRadius = 3
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
view.jx_font = .fontRegular(ofSize: 12)
|
||||||
|
view.setTitleColor(.colorAFAFAF(), for: .normal)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var desLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontRegular(ofSize: 12)
|
||||||
|
label.textColor = .colorFFFFFF(alpha: 0.6)
|
||||||
|
label.numberOfLines = 3
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var lineView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .color545458(alpha: 0.45)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let itemWidth = floor((UIScreen.width - 8 * 4 - 30) / 5)
|
||||||
|
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.itemSize = .init(width: itemWidth, height: 54)
|
||||||
|
layout.minimumLineSpacing = 9
|
||||||
|
layout.minimumInteritemSpacing = 8
|
||||||
|
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var collectionView: VPCollectionView = {
|
||||||
|
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.tabbarSafeBottomMargin, right: 0)
|
||||||
|
collectionView.register(VPEpisodeCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var menuView: VPEpisodeMenuView = {
|
||||||
|
let view = VPEpisodeMenuView()
|
||||||
|
view.didSelectedIndex = { [weak self] index in
|
||||||
|
guard let self = self else { return }
|
||||||
|
var row = 0
|
||||||
|
if index > 0 {
|
||||||
|
row = index * 30 + 10
|
||||||
|
let count = self.dataArr.count
|
||||||
|
if row >= count {
|
||||||
|
row = count - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let indexPath = IndexPath.init(row: row, section: 0)
|
||||||
|
self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)
|
||||||
|
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
vp_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleCloseButton() {
|
||||||
|
self.dismiss(animated: true) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: HWPanModalPresentable
|
||||||
|
override func panScrollable() -> UIScrollView? {
|
||||||
|
return collectionView
|
||||||
|
}
|
||||||
|
|
||||||
|
override func longFormHeight() -> PanModalHeight {
|
||||||
|
return PanModalHeightMake(.content, UIScreen.height * (2 / 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
override func showDragIndicator() -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func backgroundConfig() -> HWBackgroundConfig {
|
||||||
|
let config = HWBackgroundConfig()
|
||||||
|
config.backgroundAlpha = 0.6
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPEpisodeView {
|
||||||
|
|
||||||
|
private func vp_setupUI() {
|
||||||
|
addSubview(bgView)
|
||||||
|
addSubview(closeButton)
|
||||||
|
addSubview(coverImageView)
|
||||||
|
addSubview(videoNameLabel)
|
||||||
|
addSubview(tagView)
|
||||||
|
addSubview(desLabel)
|
||||||
|
addSubview(lineView)
|
||||||
|
addSubview(menuView)
|
||||||
|
addSubview(collectionView)
|
||||||
|
|
||||||
|
bgView.snp.makeConstraints { make in
|
||||||
|
make.left.right.top.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
closeButton.snp.makeConstraints { make in
|
||||||
|
make.right.equalToSuperview().offset(-5)
|
||||||
|
make.top.equalToSuperview().offset(5)
|
||||||
|
make.width.equalTo(40)
|
||||||
|
make.height.equalTo(40)
|
||||||
|
}
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.top.equalToSuperview().offset(45)
|
||||||
|
make.width.equalTo(64)
|
||||||
|
make.height.equalTo(82)
|
||||||
|
}
|
||||||
|
|
||||||
|
videoNameLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(10)
|
||||||
|
make.centerY.equalTo(coverImageView.snp.top).offset(20)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||||
|
}
|
||||||
|
|
||||||
|
tagView.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(videoNameLabel)
|
||||||
|
make.bottom.equalTo(coverImageView).offset(-12)
|
||||||
|
}
|
||||||
|
|
||||||
|
desLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||||
|
make.top.equalTo(coverImageView.snp.bottom).offset(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
lineView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.top.equalTo(desLabel.snp.bottom).offset(46)
|
||||||
|
make.height.equalTo(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
menuView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalTo(self.lineView)
|
||||||
|
make.bottom.equalTo(self.lineView).offset(0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
make.top.equalTo(lineView.snp.bottom).offset(14)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
|
||||||
|
extension VPEpisodeView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPEpisodeCell
|
||||||
|
cell.videoInfoModel = self.dataArr[indexPath.row]
|
||||||
|
cell.vp_isSelected = indexPath.row == currentIndex
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return self.dataArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
guard indexPath.row != currentIndex else { return }
|
||||||
|
self.didSelectedIndex?(indexPath.row)
|
||||||
|
self.dismiss(animated: true) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
if isDragging || isDecelerating {
|
||||||
|
updateMuneSelectedIndex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||||
|
isDecelerating = false
|
||||||
|
updateMuneSelectedIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
|
||||||
|
isDecelerating = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
|
isDragging = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||||
|
isDragging = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMuneSelectedIndex() {
|
||||||
|
let indexPathArr = collectionView.indexPathsForVisibleItems
|
||||||
|
|
||||||
|
var minRow = dataArr.count - 1
|
||||||
|
var maxRow = 0
|
||||||
|
|
||||||
|
for indexPath in indexPathArr {
|
||||||
|
if indexPath.row < minRow {
|
||||||
|
minRow = indexPath.row
|
||||||
|
}
|
||||||
|
if indexPath.row > maxRow {
|
||||||
|
maxRow = indexPath.row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedIndex = maxRow / 30
|
||||||
|
if menuView.selectedIndex != selectedIndex {
|
||||||
|
menuView.selectedIndex = selectedIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
212
Veloria/Class/Player/View/VPPlayerProgressView.swift
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
//
|
||||||
|
// VPPlayerProgressView.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPPlayerProgressView: UIView {
|
||||||
|
|
||||||
|
///滑动开始
|
||||||
|
var panStart: (() -> Void)?
|
||||||
|
|
||||||
|
///滑动中
|
||||||
|
var panChange: ((_ progress: CGFloat) -> Void)?
|
||||||
|
|
||||||
|
///滑动完成回调
|
||||||
|
var panFinish: ((_ progress: CGFloat) -> Void)?
|
||||||
|
|
||||||
|
var progress: CGFloat = 0 {
|
||||||
|
didSet {
|
||||||
|
if !isPaning {
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///用来记录滑动时的当前进度
|
||||||
|
private var tempProgress: CGFloat = 0
|
||||||
|
|
||||||
|
///滑动进度
|
||||||
|
private var panProgress: CGFloat = 0
|
||||||
|
|
||||||
|
var progressColor: UIColor = .colorFFFFFF(alpha: 0.2)
|
||||||
|
var currentProgress: UIColor = .colorFFFFFF()
|
||||||
|
|
||||||
|
var lineWidth: CGFloat = 2
|
||||||
|
|
||||||
|
///加载中状态
|
||||||
|
var isLoading = false {
|
||||||
|
didSet {
|
||||||
|
if isLoading {
|
||||||
|
if gradientTimer == nil {
|
||||||
|
gradientTimer = Timer.scheduledTimer(timeInterval: 0.05, target: YYWeakProxy(target: self), selector: #selector(handleGradientTimer), userInfo: nil, repeats: true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gradientTimer?.invalidate()
|
||||||
|
gradientTimer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var insets: UIEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: 0) {
|
||||||
|
didSet {
|
||||||
|
self.invalidateIntrinsicContentSize()
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private(set) lazy var panGesture: UIPanGestureRecognizer = {
|
||||||
|
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(sender:)))
|
||||||
|
return pan
|
||||||
|
}()
|
||||||
|
|
||||||
|
private(set) lazy var tagGesture: UITapGestureRecognizer = {
|
||||||
|
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(sender:)))
|
||||||
|
return tap
|
||||||
|
}()
|
||||||
|
|
||||||
|
///是否在滑动中
|
||||||
|
private var isPaning: Bool = false
|
||||||
|
|
||||||
|
private var gradientTimer: Timer?
|
||||||
|
|
||||||
|
private var gradientValue: CGFloat = 0
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
return .init(width: UIScreen.width, height: lineWidth + insets.top + insets.bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
// self.backgroundColor = progressColor
|
||||||
|
self.backgroundColor = .clear
|
||||||
|
|
||||||
|
self.addGestureRecognizer(panGesture)
|
||||||
|
self.addGestureRecognizer(tagGesture)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleGradientTimer() {
|
||||||
|
gradientValue += 0.1
|
||||||
|
if gradientValue > 1 {
|
||||||
|
gradientValue = 0
|
||||||
|
}
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func draw(_ rect: CGRect) {
|
||||||
|
super.draw(rect)
|
||||||
|
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||||
|
let width = rect.width
|
||||||
|
let height = rect.height
|
||||||
|
|
||||||
|
let progressX = insets.left
|
||||||
|
let progressY = insets.top
|
||||||
|
// let progressY = height - lineWidth
|
||||||
|
let progressWidth = width - insets.left - insets.right
|
||||||
|
|
||||||
|
if isLoading, !isPaning {
|
||||||
|
// 定义颜色空间
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let colors: [CGColor] = [
|
||||||
|
UIColor.clear.cgColor,
|
||||||
|
UIColor.white.cgColor,
|
||||||
|
UIColor.clear.cgColor
|
||||||
|
]
|
||||||
|
let locations: [CGFloat] = [0.0, gradientValue, 1.0]
|
||||||
|
|
||||||
|
guard let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let gradientRect = CGRect(x: progressX,
|
||||||
|
y: progressY,
|
||||||
|
width: progressWidth,
|
||||||
|
height: lineWidth)
|
||||||
|
|
||||||
|
// 定义渐变的起点和终点
|
||||||
|
let startPoint = CGPoint(x: rect.minX, y: rect.minY)
|
||||||
|
let endPoint = CGPoint(x: rect.maxX, y: rect.maxY)
|
||||||
|
|
||||||
|
// 裁剪到渐变区域
|
||||||
|
context.saveGState()
|
||||||
|
context.clip(to: gradientRect)
|
||||||
|
|
||||||
|
// 绘制渐变
|
||||||
|
context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: [])
|
||||||
|
} else {
|
||||||
|
var progress = self.progress
|
||||||
|
if self.isPaning {
|
||||||
|
progress = self.panProgress
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// let y = height - lineWidth
|
||||||
|
|
||||||
|
///绘制进度
|
||||||
|
let progressPath = UIBezierPath(roundedRect: CGRect(x: progressX, y: progressY, width: progressWidth, height: lineWidth), cornerRadius: lineWidth / 2)
|
||||||
|
context.addPath(progressPath.cgPath)
|
||||||
|
context.setFillColor(progressColor.cgColor)
|
||||||
|
context.fillPath()
|
||||||
|
|
||||||
|
///绘制当前进度
|
||||||
|
let currentPath = UIBezierPath(roundedRect: CGRect(x: progressX, y: progressY, width: progressWidth * progress, height: lineWidth), cornerRadius: lineWidth / 2)
|
||||||
|
context.addPath(currentPath.cgPath)
|
||||||
|
context.setFillColor(currentProgress.cgColor)
|
||||||
|
context.fillPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPPlayerProgressView {
|
||||||
|
|
||||||
|
@objc func handlePanGesture(sender: UIPanGestureRecognizer) {
|
||||||
|
|
||||||
|
switch sender.state {
|
||||||
|
case .began:
|
||||||
|
self.isPaning = true
|
||||||
|
self.tempProgress = self.progress
|
||||||
|
sender.setTranslation(CGPoint(x: 0, y: 0), in: self)
|
||||||
|
self.panStart?()
|
||||||
|
|
||||||
|
case .changed:
|
||||||
|
let point = sender.translation(in: self)
|
||||||
|
let offsetX = point.x / self.width
|
||||||
|
self.panProgress = self.tempProgress + offsetX
|
||||||
|
if self.panProgress < 0 {
|
||||||
|
self.panProgress = 0
|
||||||
|
}
|
||||||
|
self.panChange?(self.panProgress)
|
||||||
|
setNeedsDisplay()
|
||||||
|
|
||||||
|
default:
|
||||||
|
self.isPaning = false
|
||||||
|
self.panFinish?(self.panProgress)
|
||||||
|
|
||||||
|
self.panProgress = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleTapGesture(sender: UITapGestureRecognizer) {
|
||||||
|
let point = sender.location(in: self)
|
||||||
|
let offsetX = point.x / self.width
|
||||||
|
self.panFinish?(offsetX)
|
||||||
|
}
|
||||||
|
}
|
63
Veloria/Class/Player/View/VPRateSelectedCell.swift
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// VPRateSelectedCell.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPRateSelectedCell: VPCollectionViewCell {
|
||||||
|
|
||||||
|
var model: VPVideoRateModel? {
|
||||||
|
didSet {
|
||||||
|
label.text = model?.formatString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var vp_isSelected: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if vp_isSelected {
|
||||||
|
contentView.vp_setGradientBorder()
|
||||||
|
contentView.backgroundColor = .color1C2D2F(alpha: 0.6)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
contentView.vp_removeGradientBorder()
|
||||||
|
contentView.backgroundColor = .color000000(alpha: 0.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var label: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontRegular(ofSize: 14)
|
||||||
|
label.textColor = .colorFFFFFF()
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
vp_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPRateSelectedCell {
|
||||||
|
|
||||||
|
private func vp_setupUI() {
|
||||||
|
contentView.layer.cornerRadius = 6
|
||||||
|
contentView.layer.masksToBounds = true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
contentView.addSubview(label)
|
||||||
|
|
||||||
|
label.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
102
Veloria/Class/Player/View/VPRateSelectedView.swift
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
//
|
||||||
|
// VPRateSelectedView.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPRateSelectedView: UIView {
|
||||||
|
|
||||||
|
var currentRateModel: VPVideoRateModel? {
|
||||||
|
didSet {
|
||||||
|
collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var didSelected: ((_ rateModel: VPVideoRateModel) -> Void)?
|
||||||
|
|
||||||
|
private lazy var rateArr: [VPVideoRateModel] = VPVideoRateModel.getAllRate()
|
||||||
|
|
||||||
|
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.scrollDirection = .horizontal
|
||||||
|
layout.itemSize = .init(width: 70, height: 54)
|
||||||
|
layout.minimumLineSpacing = 10
|
||||||
|
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var collectionView: VPCollectionView = {
|
||||||
|
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.register(VPRateSelectedCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
collectionView.showsHorizontalScrollIndicator = false
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
let tap = UITapGestureRecognizer(target: self, action: #selector(handleDismiss))
|
||||||
|
tap.delegate = self
|
||||||
|
self.addGestureRecognizer(tap)
|
||||||
|
vp_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleDismiss() {
|
||||||
|
self.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPRateSelectedView {
|
||||||
|
|
||||||
|
private func vp_setupUI() {
|
||||||
|
addSubview(collectionView)
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview().offset(-(UIScreen.tabbarSafeBottomMargin + 85))
|
||||||
|
make.height.equalTo(54)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
|
||||||
|
extension VPRateSelectedView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let model = rateArr[indexPath.row]
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPRateSelectedCell
|
||||||
|
cell.model = model
|
||||||
|
cell.vp_isSelected = model.rate == currentRateModel?.rate
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return rateArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
let model = rateArr[indexPath.row]
|
||||||
|
self.didSelected?(model)
|
||||||
|
self.handleDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- UIGestureRecognizerDelegate --------------
|
||||||
|
extension VPRateSelectedView: UIGestureRecognizerDelegate {
|
||||||
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||||
|
if touch.view != self {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
214
Veloria/Class/Player/View/VPVideoPlayerCell.swift
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
//
|
||||||
|
// VPVideoPlayerCell.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPVideoPlayerCell: VPCollectionViewCell, VPPlayerProtocol {
|
||||||
|
|
||||||
|
var ControlViewClass: VPVideoPlayerControlView.Type {
|
||||||
|
return VPVideoPlayerControlView.self
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
weak var viewModel: VPVideoPlayViewModel? {
|
||||||
|
didSet {
|
||||||
|
self.controlView.viewModel = self.viewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortModel: VPShortModel? {
|
||||||
|
didSet {
|
||||||
|
self.controlView.shortModel = shortModel
|
||||||
|
coverImageView.vp_setImage(url: shortModel?.image_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoInfo: VPVideoInfoModel? {
|
||||||
|
didSet {
|
||||||
|
self.controlView.progress = 0
|
||||||
|
self.controlView.currentTime = 0
|
||||||
|
self.controlView.durationTime = 0
|
||||||
|
|
||||||
|
self.controlView.videoInfo = videoInfo
|
||||||
|
|
||||||
|
player.setPlayUrl(url: videoInfo?.video_url ?? "")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isLoop = true {
|
||||||
|
didSet {
|
||||||
|
player.isLoop = isLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private lazy var player: VPPlayer = {
|
||||||
|
let player = VPPlayer()
|
||||||
|
player.playerView = playerView
|
||||||
|
player.delegate = self
|
||||||
|
return player
|
||||||
|
}()
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private lazy var playerView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var coverImageView: VPImageView = {
|
||||||
|
let imageView = VPImageView()
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var controlView: VPVideoPlayerControlView = {
|
||||||
|
let view = ControlViewClass.init()
|
||||||
|
view.panProgressFinishBlock = { [weak self] progress in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let duration = CGFloat(self.player.duration)
|
||||||
|
let toTime = progress * duration
|
||||||
|
self.player.seekToTime(toTime: Int(toTime))
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
vp_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//MARK: VPPlayerProtocol
|
||||||
|
var playerFinishHadle: (() -> Void)?
|
||||||
|
|
||||||
|
var isCurrent: Bool = false {
|
||||||
|
didSet {
|
||||||
|
controlView.isCurrent = isCurrent
|
||||||
|
if !isCurrent {
|
||||||
|
// self.player.replay()
|
||||||
|
// self.player.seekToTime(toTime: 0)
|
||||||
|
self.coverImageView.isHidden = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var duration: Int {
|
||||||
|
get {
|
||||||
|
return player.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentPosition: Int {
|
||||||
|
get {
|
||||||
|
player.currentPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rate: Float {
|
||||||
|
set {
|
||||||
|
player.rate = newValue
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
player.rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepare() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
player.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func pause() {
|
||||||
|
player.pause()
|
||||||
|
}
|
||||||
|
|
||||||
|
func replay() {
|
||||||
|
player.replay()
|
||||||
|
}
|
||||||
|
|
||||||
|
func seekToTime(toTime: Int) {
|
||||||
|
player.seekToTime(toTime: toTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPVideoPlayerCell {
|
||||||
|
|
||||||
|
private func vp_setupUI() {
|
||||||
|
contentView.addSubview(playerView)
|
||||||
|
contentView.addSubview(coverImageView)
|
||||||
|
contentView.addSubview(controlView)
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
playerView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
controlView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- SPPlayerDelegate --------------
|
||||||
|
extension VPVideoPlayerCell: VPPlayerDelegate {
|
||||||
|
|
||||||
|
func vp_playCompletion(_ player: VPPlayer) {
|
||||||
|
// self.playerFinishHadle?()
|
||||||
|
self.viewModel?.handlePlayFinish?()
|
||||||
|
}
|
||||||
|
|
||||||
|
func vp_playLoadingEnd(_ player: VPPlayer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func vp_playTimeChanged(_ player: VPPlayer, currentTime: Int, duration: Int) {
|
||||||
|
controlView.progress = CGFloat(currentTime) / CGFloat(duration)
|
||||||
|
controlView.currentTime = currentTime
|
||||||
|
controlView.durationTime = duration
|
||||||
|
self.viewModel?.handlePlayTimeDidChange?(currentTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func vp_firstRenderedStart(_ player: VPPlayer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func vp_player(_ player: VPPlayer, playStateDidChanged state: VPPlayer.PlayState) {
|
||||||
|
if state == .playing {
|
||||||
|
self.coverImageView.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func vp_player(_ player: VPPlayer, loadStateDidChange state: VPPlayer.LoadState) {
|
||||||
|
if state == .prepare || state == .stalled {
|
||||||
|
self.controlView.isLoading = true
|
||||||
|
} else {
|
||||||
|
self.controlView.isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func vp_playerReadyToPlay(_ player: VPPlayer) {
|
||||||
|
self.seekToTime(toTime: (videoInfo?.play_seconds ?? 0) / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
299
Veloria/Class/Player/View/VPVideoPlayerControlView.swift
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
//
|
||||||
|
// VPVideoPlayerControlView.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by 湖南秦九 on 2025/5/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPVideoPlayerControlView: UIView {
|
||||||
|
|
||||||
|
weak var viewModel: VPVideoPlayViewModel? {
|
||||||
|
didSet {
|
||||||
|
viewModel?.addObserver(self, forKeyPath: "isPlaying", context: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortModel: VPShortModel? {
|
||||||
|
didSet {
|
||||||
|
updateCollectButtonState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoInfo: VPVideoInfoModel? {
|
||||||
|
didSet {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///滑动进度条
|
||||||
|
var panProgressFinishBlock: ((_ progress: CGFloat) -> Void)?
|
||||||
|
|
||||||
|
///0-1
|
||||||
|
var progress: CGFloat = 0 {
|
||||||
|
didSet {
|
||||||
|
progressView.progress = progress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///加载中状态
|
||||||
|
var isLoading = false {
|
||||||
|
didSet {
|
||||||
|
progressView.isLoading = isLoading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var durationTime: Int = 0
|
||||||
|
var currentTime: Int = 0
|
||||||
|
|
||||||
|
var isCurrent: Bool = false {
|
||||||
|
didSet {
|
||||||
|
updatePlayIconState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var hiddenPlayButtonTimer: Timer?
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private(set) lazy var progressView: VPPlayerProgressView = {
|
||||||
|
let view = VPPlayerProgressView()
|
||||||
|
view.panStart = { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.panProgressStart()
|
||||||
|
}
|
||||||
|
view.panChange = { [weak self] progress in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.panProgressChange(progress: progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
view.panFinish = { [weak self] progress in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.panProgressFinish(progress: progress)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private(set) lazy var rightToolView: UIStackView = {
|
||||||
|
let view = UIStackView(arrangedSubviews: [collectButton])
|
||||||
|
view.axis = .vertical
|
||||||
|
view.spacing = 28
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
///收藏按钮
|
||||||
|
private lazy var collectButton: UIButton = {
|
||||||
|
let button = createRightButton(title: "0", image: UIImage(named: "collect_icon_01"), selectedImage: UIImage(named: "collect_icon_01_selected"))
|
||||||
|
button.addTarget(self, action: #selector(handleCollectButton), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var playButton: UIControl = {
|
||||||
|
let button = UIControl()
|
||||||
|
// let button = UIButton(type: .custom)
|
||||||
|
// button.setImage(UIImage(named: "pause_icon_01"), for: .normal)
|
||||||
|
// button.setImage(UIImage(named: "play_icon_01"), for: .selected)
|
||||||
|
button.addEffectView(style: .light)
|
||||||
|
button.addTarget(self, action: #selector(handlePlayButton), for: .touchUpInside)
|
||||||
|
button.layer.cornerRadius = 6
|
||||||
|
button.layer.masksToBounds = true
|
||||||
|
button.isHidden = true
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var playIconImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView()
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
viewModel?.removeObserver(self, forKeyPath: "isPlaying")
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: VPVideoAPI.updateShortCollectStateNotification, object: nil)
|
||||||
|
|
||||||
|
let tap = UITapGestureRecognizer(target: self, action: #selector(handleScreen))
|
||||||
|
tap.delegate = self
|
||||||
|
self.addGestureRecognizer(tap)
|
||||||
|
|
||||||
|
vp_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
if keyPath == "isPlaying" {
|
||||||
|
updatePlayIconState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRightButton(title: String?, selectedTitle: String? = nil, image: UIImage?, selectedImage: UIImage? = nil) -> UIButton {
|
||||||
|
let button = JXButton(type: .custom)
|
||||||
|
button.titleDirection = .down
|
||||||
|
button.space = 8
|
||||||
|
button.setImage(image, for: .normal)
|
||||||
|
button.setImage(selectedImage, for: .selected)
|
||||||
|
button.setImage(selectedImage, for: [.selected, .highlighted])
|
||||||
|
button.setTitle(title, for: .normal);
|
||||||
|
button.setTitle(selectedTitle, for: .selected);
|
||||||
|
button.setTitle(selectedTitle, for: [.selected, .highlighted])
|
||||||
|
button.setTitleColor(.colorFFFFFF(), for: .normal)
|
||||||
|
button.setTitleColor(.colorFFBD36(), for: .selected)
|
||||||
|
button.setTitleColor(.colorFFBD36(), for: [.selected, .highlighted])
|
||||||
|
button.jx_font = .fontRegular(ofSize: 10)
|
||||||
|
return button
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func updatePlayIconState() {
|
||||||
|
let isPlaying = self.viewModel?.isPlaying ?? false
|
||||||
|
if isCurrent {
|
||||||
|
if isPlaying {
|
||||||
|
playIconImageView.image = UIImage(named: "pause_icon_01")
|
||||||
|
hiddenPlayButtonTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(handleHiddenPlayButtonTimer), userInfo: nil, repeats: false)
|
||||||
|
} else {
|
||||||
|
playIconImageView.image = UIImage(named: "play_icon_01")
|
||||||
|
hiddenPlayButtonTimer?.invalidate()
|
||||||
|
hiddenPlayButtonTimer = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
playButton.isHidden = true
|
||||||
|
playIconImageView.image = UIImage(named: "pause_icon_01")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPVideoPlayerControlView {
|
||||||
|
@objc private func handlePlayButton() {
|
||||||
|
self.hadlePlayAndOrPaused()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleScreen() {
|
||||||
|
if self.viewModel?.isPlaying != true { return }
|
||||||
|
|
||||||
|
self.playButton.isHidden = !self.playButton.isHidden
|
||||||
|
|
||||||
|
if self.playButton.isHidden {
|
||||||
|
self.hiddenPlayButtonTimer?.invalidate()
|
||||||
|
self.hiddenPlayButtonTimer = nil
|
||||||
|
} else {
|
||||||
|
self.hiddenPlayButtonTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(handleHiddenPlayButtonTimer), userInfo: nil, repeats: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func hadlePlayAndOrPaused() {
|
||||||
|
self.viewModel?.handlePauseOrPlay?()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleHiddenPlayButtonTimer() {
|
||||||
|
if self.viewModel?.isPlaying != true { return }
|
||||||
|
|
||||||
|
self.playButton.isHidden = true
|
||||||
|
hiddenPlayButtonTimer?.invalidate()
|
||||||
|
hiddenPlayButtonTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleCollectButton() {
|
||||||
|
guard let shortPlayId = self.videoInfo?.short_play_id else { return }
|
||||||
|
guard let videoId = self.videoInfo?.short_play_video_id else { return }
|
||||||
|
|
||||||
|
let isCollect = !(self.shortModel?.is_collect ?? false)
|
||||||
|
|
||||||
|
VPVideoAPI.requestCollectShort(isCollect: isCollect, shortPlayId: shortPlayId, videoId: videoId) { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
var count = self.shortModel?.collect_total ?? 0
|
||||||
|
if isCollect {
|
||||||
|
count += 1
|
||||||
|
} else {
|
||||||
|
count -= 1
|
||||||
|
}
|
||||||
|
if count < 0 {
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
self.shortModel?.collect_total = count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func updateShortCollectStateNotification(sender: Notification) {
|
||||||
|
guard let userInfo = sender.userInfo else { return }
|
||||||
|
guard let shortPlayId = userInfo["id"] as? String else { return }
|
||||||
|
guard let isCollect = userInfo["state"] as? Bool else { return }
|
||||||
|
guard shortPlayId == self.videoInfo?.short_play_id else { return }
|
||||||
|
|
||||||
|
self.shortModel?.is_collect = isCollect;
|
||||||
|
updateCollectButtonState()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateCollectButtonState() {
|
||||||
|
self.collectButton.isSelected = self.shortModel?.is_collect ?? false
|
||||||
|
self.collectButton.setTitle("\(self.shortModel?.collect_total ?? 0)", for: .normal)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPVideoPlayerControlView {
|
||||||
|
|
||||||
|
private func vp_setupUI() {
|
||||||
|
addSubview(progressView)
|
||||||
|
|
||||||
|
addSubview(rightToolView)
|
||||||
|
addSubview(playButton)
|
||||||
|
playButton.addSubview(playIconImageView)
|
||||||
|
|
||||||
|
rightToolView.snp.makeConstraints { make in
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
make.bottom.equalToSuperview().offset(-110)
|
||||||
|
}
|
||||||
|
|
||||||
|
playButton.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
make.width.equalTo(70)
|
||||||
|
make.height.equalTo(50)
|
||||||
|
}
|
||||||
|
|
||||||
|
playIconImageView.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension VPVideoPlayerControlView {
|
||||||
|
///滑动进度开始
|
||||||
|
private func panProgressStart() {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
///滑动进度中
|
||||||
|
private func panProgressChange(progress: CGFloat) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
///滑动进度结束
|
||||||
|
private func panProgressFinish(progress: CGFloat) {
|
||||||
|
self.panProgressFinishBlock?(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- UIGestureRecognizerDelegate --------------
|
||||||
|
extension VPVideoPlayerControlView: UIGestureRecognizerDelegate {
|
||||||
|
|
||||||
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||||
|
if touch.view != self {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
Veloria/Class/Player/ViewModel/VPVideoPlayViewModel.swift
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// VPVideoPlayViewModel.swift
|
||||||
|
// Veloria
|
||||||
|
//
|
||||||
|
// Created by Veloria on 2025/5/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class VPVideoPlayViewModel: NSObject {
|
||||||
|
|
||||||
|
@objc dynamic var isPlaying: Bool = true
|
||||||
|
|
||||||
|
|
||||||
|
var currentPlayer: VPPlayerProtocol? {
|
||||||
|
didSet {
|
||||||
|
oldValue?.isCurrent = false
|
||||||
|
oldValue?.pause()
|
||||||
|
|
||||||
|
// self.currentPlayer?.playerFinishHadle = { [weak self] in
|
||||||
|
// self?.handlePlayFinish?()
|
||||||
|
// }
|
||||||
|
self.currentPlayer?.isCurrent = true
|
||||||
|
self.currentPlayer?.rate = rateModel.rate.getRate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///倍速播放
|
||||||
|
@objc dynamic lazy var rateModel = VPVideoRateModel(rate: .x1) {
|
||||||
|
didSet {
|
||||||
|
self.currentPlayer?.rate = rateModel.rate.getRate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///设置进度
|
||||||
|
func seekToTime(toTime: Int) {
|
||||||
|
self.currentPlayer?.seekToTime(toTime: toTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
///点暂停或播放
|
||||||
|
var handlePauseOrPlay: (() -> Void)?
|
||||||
|
///播放完成
|
||||||
|
var handlePlayFinish: (() -> Void)?
|
||||||
|
///播放进度变更
|
||||||
|
var handlePlayTimeDidChange: ((_ time: Int) -> Void)?
|
||||||
|
///选集
|
||||||
|
var handleEpisode: (() -> Void)?
|
||||||
|
}
|
@ -37,16 +37,17 @@ class VPLocalizedManager: NSObject {
|
|||||||
// 获取当前语言代码(如果用户未手动设置,则返回系统语言)
|
// 获取当前语言代码(如果用户未手动设置,则返回系统语言)
|
||||||
var currentLocalizedKey: String {
|
var currentLocalizedKey: String {
|
||||||
get {
|
get {
|
||||||
var key = (UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) ?? Locale.preferredLanguages.first) ?? "en"
|
// var key = (UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) ?? Locale.preferredLanguages.first) ?? "en"
|
||||||
if key.contains("zh-Hans") {
|
// if key.contains("zh-Hans") {
|
||||||
key = "zh"
|
// key = "zh"
|
||||||
} else if key.contains("zh-Hant") {
|
// } else if key.contains("zh-Hant") {
|
||||||
key = "zh_hk"
|
// key = "zh_hk"
|
||||||
} else {
|
// } else {
|
||||||
let arr = key.components(separatedBy: "-")
|
// let arr = key.components(separatedBy: "-")
|
||||||
key = arr.first ?? "en"
|
// key = arr.first ?? "en"
|
||||||
}
|
// }
|
||||||
return key
|
// return key
|
||||||
|
return "en"
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
UserDefaults.standard.set(newValue, forKey: LocalizedUserDefaultsKey)
|
UserDefaults.standard.set(newValue, forKey: LocalizedUserDefaultsKey)
|
||||||
|
22
Veloria/Source/Assets.xcassets/icon/arrow_left_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Symbol@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Symbol@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Veloria/Source/Assets.xcassets/icon/arrow_left_icon_01.imageset/Symbol@2x.png
vendored
Normal file
After Width: | Height: | Size: 292 B |
BIN
Veloria/Source/Assets.xcassets/icon/arrow_left_icon_01.imageset/Symbol@3x.png
vendored
Normal file
After Width: | Height: | Size: 410 B |
22
Veloria/Source/Assets.xcassets/icon/arrow_right_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Veloria/Source/Assets.xcassets/icon/arrow_right_icon_01.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 198 B |
BIN
Veloria/Source/Assets.xcassets/icon/arrow_right_icon_01.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 247 B |
22
Veloria/Source/Assets.xcassets/icon/arrow_up_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Veloria/Source/Assets.xcassets/icon/arrow_up_icon_01.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 250 B |
BIN
Veloria/Source/Assets.xcassets/icon/arrow_up_icon_01.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 275 B |
22
Veloria/Source/Assets.xcassets/icon/close_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Veloria/Source/Assets.xcassets/icon/close_icon_01.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 500 B |
BIN
Veloria/Source/Assets.xcassets/icon/close_icon_01.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 710 B |
22
Veloria/Source/Assets.xcassets/icon/collect_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Vector@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Vector@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Veloria/Source/Assets.xcassets/icon/collect_icon_01.imageset/Vector@2x.png
vendored
Normal file
After Width: | Height: | Size: 820 B |
BIN
Veloria/Source/Assets.xcassets/icon/collect_icon_01.imageset/Vector@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
22
Veloria/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Veloria/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
Veloria/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 5.9 KiB |
22
Veloria/Source/Assets.xcassets/icon/ep_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Veloria/Source/Assets.xcassets/icon/ep_icon_01.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 400 B |
BIN
Veloria/Source/Assets.xcassets/icon/ep_icon_01.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 526 B |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.2 KiB |
@ -5,12 +5,12 @@
|
|||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Component 20@2x.png",
|
"filename" : "观看历史.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Component 20@3x.png",
|
"filename" : "观看历史@3x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
|
BIN
Veloria/Source/Assets.xcassets/icon/history_icon_01.imageset/观看历史.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
Veloria/Source/Assets.xcassets/icon/history_icon_01.imageset/观看历史@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.2 KiB |
22
Veloria/Source/Assets.xcassets/icon/pause_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame 76@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame 76@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Veloria/Source/Assets.xcassets/icon/pause_icon_01.imageset/Frame 76@2x.png
vendored
Normal file
After Width: | Height: | Size: 305 B |
BIN
Veloria/Source/Assets.xcassets/icon/pause_icon_01.imageset/Frame 76@3x.png
vendored
Normal file
After Width: | Height: | Size: 438 B |
22
Veloria/Source/Assets.xcassets/icon/play_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame 75@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Frame 75@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Veloria/Source/Assets.xcassets/icon/play_icon_01.imageset/Frame 75@2x.png
vendored
Normal file
After Width: | Height: | Size: 544 B |
BIN
Veloria/Source/Assets.xcassets/icon/play_icon_01.imageset/Frame 75@3x.png
vendored
Normal file
After Width: | Height: | Size: 772 B |
@ -9,3 +9,4 @@
|
|||||||
#import <MJRefresh/MJRefresh.h>
|
#import <MJRefresh/MJRefresh.h>
|
||||||
#import <ZFPlayer.h>
|
#import <ZFPlayer.h>
|
||||||
#import <KTVHTTPCache/KTVHTTPCache.h>
|
#import <KTVHTTPCache/KTVHTTPCache.h>
|
||||||
|
#import <HWPanModal/HWPanModal.h>
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
"All" = "All";
|
"All" = "All";
|
||||||
"Drama Champions" = "Drama Champions";
|
"Drama Champions" = "Drama Champions";
|
||||||
"Explore" = "Explore";
|
"Explore" = "Explore";
|
||||||
|
"EP.%@" = "EP.%@";
|
||||||
|
"All %@ Episodes" = "All %@ Episodes";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
13
app账号信息.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
湖南秦九开发者账号信息
|
||||||
|
认证人 :张文祺
|
||||||
|
认证设备 :Mac Mini
|
||||||
|
手机型号 :MU9D3CH/A
|
||||||
|
设备序列号:K73J20237W
|
||||||
|
手机号码 :18173178983
|
||||||
|
企业邮箱 :app@qjwl168.com密码:1q1w1e1r
|
||||||
|
D-U-N-S :616751820
|
||||||
|
公司主体 :Hunan Qinjiu Network Technology Co., Ltd.
|
||||||
|
公司地址 :9010, Xijing Apartment Sanqi Shopping Plaza, No.383, Jinxing M. Road
|
||||||
|
认证官网 :https://www.qjwl168.com
|
||||||
|
账号 :hn.qinjiu.developer@icloud.com
|
||||||
|
密码 :Discover2024
|