├── .gitignore
├── LICENSE
├── MPPlayerDemo
├── MPPlayerDemo.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
├── MPPlayerDemo.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── MPPlayerDemo
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── comment.imageset
│ │ │ ├── Contents.json
│ │ │ └── comment.png
│ │ ├── defaultUserIcon.imageset
│ │ │ ├── Contents.json
│ │ │ ├── defaultUserIcon.png
│ │ │ ├── defaultUserIcon@2x.png
│ │ │ └── defaultUserIcon@3x.png
│ │ ├── icon_play_pause.imageset
│ │ │ ├── Contents.json
│ │ │ ├── icon_play_pause@2x.png
│ │ │ └── icon_play_pause@3x.png
│ │ ├── icon_titlebar_whiteback.imageset
│ │ │ ├── Contents.json
│ │ │ ├── icon_titlebar_whiteback@2x.png
│ │ │ └── icon_titlebar_whiteback@3x.png
│ │ ├── img_video_loading.imageset
│ │ │ ├── Contents.json
│ │ │ ├── img_video_loading@2x.png
│ │ │ └── img_video_loading@3x.png
│ │ ├── like.imageset
│ │ │ ├── Contents.json
│ │ │ └── like.png
│ │ ├── loading_bgView.imageset
│ │ │ ├── Contents.json
│ │ │ └── loading_bgView.png
│ │ ├── logo.imageset
│ │ │ ├── Contents.json
│ │ │ └── logo.png
│ │ ├── new_allPause_44x44_.imageset
│ │ │ ├── Contents.json
│ │ │ ├── new_allPause_44x44_@2x.png
│ │ │ └── new_allPause_44x44_@3x.png
│ │ ├── new_allPlay_44x44_.imageset
│ │ │ ├── Contents.json
│ │ │ ├── new_allPlay_44x44_@2x.png
│ │ │ └── new_allPlay_44x44_@3x.png
│ │ ├── share.imageset
│ │ │ ├── Contents.json
│ │ │ └── share.png
│ │ └── zfplayer_back.imageset
│ │ │ ├── Contents.json
│ │ │ ├── zfplayer_back@2x.png
│ │ │ └── zfplayer_back@3x.png
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── CardLayout
│ │ ├── MPCardStackLayout.h
│ │ └── MPCardStackLayout.m
│ ├── Controller
│ │ ├── MPCardLayoutViewController.h
│ │ ├── MPCardLayoutViewController.m
│ │ ├── MPDetailViewController.h
│ │ ├── MPDetailViewController.m
│ │ ├── MPListViewController.h
│ │ ├── MPListViewController.m
│ │ ├── MPUserDynamicDetailViewController.h
│ │ ├── MPUserDynamicDetailViewController.m
│ │ ├── MPUserDynamicViewController.h
│ │ ├── MPUserDynamicViewController.m
│ │ ├── MPWaterFallViewController.h
│ │ └── MPWaterFallViewController.m
│ ├── Info.plist
│ ├── Model
│ │ ├── ZFTableData.h
│ │ ├── ZFTableData.m
│ │ └── data.json
│ ├── Other
│ │ ├── NSString+Size.h
│ │ ├── NSString+Size.m
│ │ ├── ZFTableViewCellLayout.h
│ │ └── ZFTableViewCellLayout.m
│ ├── Player
│ │ ├── MPPlayableProtocol.h
│ │ ├── MPPlayerController.h
│ │ ├── MPPlayerController.m
│ │ ├── MPPreLoaderModel.h
│ │ ├── MPPreLoaderModel.m
│ │ ├── MPlayerAttributeManager.h
│ │ ├── MPlayerAttributeManager.m
│ │ ├── ZFDouYinControlView.h
│ │ └── ZFDouYinControlView.m
│ ├── Transition
│ │ ├── MPTransition.h
│ │ ├── MPTransition.m
│ │ ├── MPUserDynamicTransition.h
│ │ └── MPUserDynamicTransition.m
│ ├── View
│ │ ├── MPCardLayoutCell.h
│ │ ├── MPCardLayoutCell.m
│ │ ├── MPUserDynamicCollectionViewCell.h
│ │ ├── MPUserDynamicCollectionViewCell.m
│ │ ├── MPWaterFallCollectionViewCell.h
│ │ ├── MPWaterFallCollectionViewCell.m
│ │ ├── ZFDouYinCell.h
│ │ ├── ZFDouYinCell.m
│ │ ├── ZFTableViewCell.h
│ │ └── ZFTableViewCell.m
│ ├── ViewController.h
│ ├── ViewController.m
│ ├── WaterFall
│ │ ├── MPWaterFallLayout.h
│ │ └── MPWaterFallLayout.m
│ └── main.m
├── MPPlayerDemoTests
│ ├── Info.plist
│ └── MPPlayerDemoTests.m
├── MPPlayerDemoUITests
│ ├── Info.plist
│ └── MPPlayerDemoUITests.m
├── Podfile
└── Podfile.lock
├── README.md
└── playerDemo.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | # CocoaPods
34 | #
35 | # We recommend against adding the Pods directory to your .gitignore. However
36 | # you should judge for yourself, the pros and cons are mentioned at:
37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
38 | #
39 | Pods/
40 | #
41 | # Add this line if you want to avoid checking in source code from the Xcode workspace
42 | # *.xcworkspace
43 |
44 | # Carthage
45 | #
46 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
47 | # Carthage/Checkouts
48 |
49 | Carthage/Build/
50 |
51 | # fastlane
52 | #
53 | # It is recommended to not store the screenshots in the git repo.
54 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
55 | # For more information about the recommended setup visit:
56 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
57 |
58 | fastlane/report.xml
59 | fastlane/Preview.html
60 | fastlane/screenshots/**/*.png
61 | fastlane/test_output
62 |
63 | # Code Injection
64 | #
65 | # After new code Injection tools there's a generated folder /iOSInjectionProject
66 | # https://github.com/johnno1962/injectionforxcode
67 |
68 | iOSInjectionProject/
69 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Maple
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (nonatomic, strong) UIWindow *window;
14 |
15 | @end
16 |
17 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import "AppDelegate.h"
10 |
11 | @interface AppDelegate ()
12 |
13 | @end
14 |
15 | @implementation AppDelegate
16 |
17 |
18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
19 | // Override point for customization after application launch.
20 | return YES;
21 | }
22 |
23 | @end
24 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/comment.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "comment.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/comment.imageset/comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/comment.imageset/comment.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/defaultUserIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "defaultUserIcon.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "defaultUserIcon@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "defaultUserIcon@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/defaultUserIcon.imageset/defaultUserIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/defaultUserIcon.imageset/defaultUserIcon.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/defaultUserIcon.imageset/defaultUserIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/defaultUserIcon.imageset/defaultUserIcon@2x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/defaultUserIcon.imageset/defaultUserIcon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/defaultUserIcon.imageset/defaultUserIcon@3x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/icon_play_pause.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icon_play_pause@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "icon_play_pause@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/icon_play_pause.imageset/icon_play_pause@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/icon_play_pause.imageset/icon_play_pause@2x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/icon_play_pause.imageset/icon_play_pause@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/icon_play_pause.imageset/icon_play_pause@3x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/icon_titlebar_whiteback.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "icon_titlebar_whiteback@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "icon_titlebar_whiteback@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/icon_titlebar_whiteback.imageset/icon_titlebar_whiteback@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/icon_titlebar_whiteback.imageset/icon_titlebar_whiteback@2x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/icon_titlebar_whiteback.imageset/icon_titlebar_whiteback@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/icon_titlebar_whiteback.imageset/icon_titlebar_whiteback@3x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/img_video_loading.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "img_video_loading@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "img_video_loading@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/img_video_loading.imageset/img_video_loading@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/img_video_loading.imageset/img_video_loading@2x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/img_video_loading.imageset/img_video_loading@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/img_video_loading.imageset/img_video_loading@3x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/like.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "like.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/like.imageset/like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/like.imageset/like.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/loading_bgView.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "loading_bgView.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/loading_bgView.imageset/loading_bgView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/loading_bgView.imageset/loading_bgView.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "logo.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/logo.imageset/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/logo.imageset/logo.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/new_allPause_44x44_.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "new_allPause_44x44_@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "new_allPause_44x44_@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/new_allPause_44x44_.imageset/new_allPause_44x44_@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/new_allPause_44x44_.imageset/new_allPause_44x44_@2x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/new_allPause_44x44_.imageset/new_allPause_44x44_@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/new_allPause_44x44_.imageset/new_allPause_44x44_@3x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/new_allPlay_44x44_.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "new_allPlay_44x44_@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "new_allPlay_44x44_@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/new_allPlay_44x44_.imageset/new_allPlay_44x44_@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/new_allPlay_44x44_.imageset/new_allPlay_44x44_@2x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/new_allPlay_44x44_.imageset/new_allPlay_44x44_@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/new_allPlay_44x44_.imageset/new_allPlay_44x44_@3x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/share.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "share.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/share.imageset/share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/share.imageset/share.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/zfplayer_back.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "zfplayer_back@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "zfplayer_back@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/zfplayer_back.imageset/zfplayer_back@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/zfplayer_back.imageset/zfplayer_back@2x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/zfplayer_back.imageset/zfplayer_back@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/MPPlayerDemo/MPPlayerDemo/Assets.xcassets/zfplayer_back.imageset/zfplayer_back@3x.png
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/CardLayout/MPCardStackLayout.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPCardStackLayout.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MPCardStackLayout : UICollectionViewLayout
14 |
15 | @property (nonatomic) CGSize itemSize;
16 | @property (nonatomic) CGFloat spacing;
17 | @property (nonatomic) NSInteger maximumVisibleItems;
18 |
19 | @end
20 |
21 | NS_ASSUME_NONNULL_END
22 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/CardLayout/MPCardStackLayout.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPCardStackLayout.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPCardStackLayout.h"
10 |
11 | @implementation MPCardStackLayout
12 |
13 | - (instancetype)init
14 | {
15 | self = [super init];
16 | self.itemSize = CGSizeMake(250, 400);
17 | self.spacing = 4;
18 | self.maximumVisibleItems = 4;
19 | return self;
20 | }
21 |
22 | - (void)prepareLayout
23 | {
24 | [super prepareLayout];
25 | NSAssert(self.collectionView.numberOfSections == 1, @"不支持多个Section");
26 | }
27 |
28 | - (CGSize)collectionViewContentSize
29 | {
30 | if (self.collectionView == nil) {
31 | return CGSizeZero;
32 | }
33 | NSInteger itemsCount = [self.collectionView numberOfItemsInSection:0];
34 | return CGSizeMake(self.collectionView.bounds.size.width * itemsCount, self.collectionView.bounds.size.height);
35 | }
36 |
37 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
38 | {
39 | if (self.collectionView == nil)
40 | return nil;
41 | NSInteger totalItemsCount = [self.collectionView numberOfItemsInSection:0];
42 | NSInteger tmp = (int)self.collectionView.contentOffset.x / (int)self.collectionView.bounds.size.width;
43 | NSInteger minVisibleIndex = MAX(0, tmp);
44 | NSInteger maxVisibleIndex = MIN(totalItemsCount, minVisibleIndex + self.maximumVisibleItems);
45 |
46 | CGFloat contentCenterX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width / 2;
47 | NSInteger deltaOffset = (int)self.collectionView.contentOffset.x % (int)self.collectionView.bounds.size.width;
48 | CGFloat percentageDeltaOffset = deltaOffset / self.collectionView.bounds.size.width;
49 |
50 | NSMutableArray *attributes = [[NSMutableArray alloc] init];
51 | for (NSInteger i = minVisibleIndex; i < maxVisibleIndex; i++) {
52 | UICollectionViewLayoutAttributes *attribute = [self conputeLayoutAttributesForItem:[NSIndexPath indexPathForRow:i inSection:0] minVisibleIndex:minVisibleIndex contentCenterX:contentCenterX deltaOffset:deltaOffset percentageDeltaOffset:percentageDeltaOffset];
53 | [attributes addObject:attribute];
54 | }
55 | return attributes;
56 | }
57 |
58 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
59 | {
60 | return YES;
61 | }
62 |
63 | - (UICollectionViewLayoutAttributes *)conputeLayoutAttributesForItem: (NSIndexPath *)indexPath minVisibleIndex: (NSInteger)minVisibleIndex contentCenterX: (CGFloat)contentCenterX deltaOffset: (CGFloat)deltaOffset percentageDeltaOffset: (CGFloat)percentageDeltaOffset {
64 | if (self.collectionView == nil) {
65 | return [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
66 | }
67 | UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
68 | NSInteger cardIndex = indexPath.row - minVisibleIndex;
69 | attributes.size = self.itemSize;
70 | CGFloat scale = 1.0 - cardIndex * 0.1;
71 | contentCenterX -= 20;
72 | // 宽度减少的值
73 | CGFloat deltaX = self.itemSize.width * cardIndex * 0.1;
74 | CGFloat centerX = contentCenterX + self.spacing * cardIndex + deltaX;
75 | CGFloat centerY = CGRectGetMidY(self.collectionView.bounds);
76 | CGPoint center = CGPointMake(centerX, centerY);
77 | attributes.zIndex = self.maximumVisibleItems - cardIndex;
78 | attributes.size = self.itemSize;
79 | if (cardIndex == 0) {
80 | center.x -= deltaOffset;
81 | attributes.transform = CGAffineTransformIdentity;
82 | }else if (cardIndex >= 1 && cardIndex < self.maximumVisibleItems) {
83 | scale = scale + percentageDeltaOffset * 0.1;
84 | attributes.transform = CGAffineTransformMakeScale(scale, scale);
85 | center.x -= (self.spacing + 0.1 * self.itemSize.width) * percentageDeltaOffset;
86 | if (cardIndex == self.maximumVisibleItems - 1) {
87 | attributes.alpha = percentageDeltaOffset;
88 | }
89 | }
90 | attributes.center = center;
91 | return attributes;
92 | }
93 |
94 |
95 | // MARK: - Setter
96 | - (void)setItemSize:(CGSize)itemSize
97 | {
98 | _itemSize = itemSize;
99 | if (self.collectionView != nil) {
100 | [self invalidateLayout];
101 | }
102 | }
103 |
104 | - (void)setSpacing:(CGFloat)spacing
105 | {
106 | _spacing = spacing;
107 | if (self.collectionView != nil) {
108 | [self invalidateLayout];
109 | }
110 | }
111 |
112 | - (void)setMaximumVisibleItems:(NSInteger)maximumVisibleItems
113 | {
114 | _maximumVisibleItems = maximumVisibleItems;
115 | if (self.collectionView != nil) {
116 | [self invalidateLayout];
117 | }
118 | }
119 |
120 | @end
121 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPCardLayoutViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPCardLayoutViewController.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MPCardLayoutViewController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPCardLayoutViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPCardLayoutViewController.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPCardLayoutViewController.h"
10 | #import "ZFUtilities.h"
11 | #import "ZFTableData.h"
12 | #import "MPCardStackLayout.h"
13 | #import "MPCardLayoutCell.h"
14 | #import
15 |
16 | @interface MPCardLayoutViewController ()
17 |
18 |
19 | @property (nonatomic, strong) UICollectionView *collectionView;
20 | @property (nonatomic, strong) NSMutableArray *dataSource;
21 |
22 | @end
23 |
24 | @implementation MPCardLayoutViewController
25 |
26 | - (void)viewDidLoad
27 | {
28 | [super viewDidLoad];
29 | [self requestData];
30 | [self setup];
31 | }
32 |
33 | - (void)requestData
34 | {
35 | NSString *path = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"];
36 | NSData *data = [NSData dataWithContentsOfFile:path];
37 | NSDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
38 |
39 | self.dataSource = @[].mutableCopy;
40 | NSArray *videoList = [rootDict objectForKey:@"list"];
41 | for (NSDictionary *dataDic in videoList) {
42 | ZFTableData *data = [[ZFTableData alloc] init];
43 | [data setValuesForKeysWithDictionary:dataDic];
44 | [self.dataSource addObject:data];
45 | }
46 | }
47 |
48 | - (void)setup
49 | {
50 | self.view.backgroundColor = [UIColor whiteColor];
51 | MPCardStackLayout *layout = [[MPCardStackLayout alloc] init];
52 | CGFloat itemW = self.view.bounds.size.width - 16 - 20 * 2 - 15;
53 | CGFloat itemH = itemW + 97;
54 | layout.itemSize = CGSizeMake(itemW, itemH);
55 | self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
56 |
57 | [self.collectionView registerClass:[MPCardLayoutCell class] forCellWithReuseIdentifier:@"MPCardLayoutCell"];
58 | self.collectionView.pagingEnabled = YES;
59 | self.collectionView.showsVerticalScrollIndicator = NO;
60 | self.collectionView.showsHorizontalScrollIndicator = NO;
61 | self.collectionView.backgroundColor = [UIColor whiteColor];
62 | self.collectionView.delegate = self;
63 | self.collectionView.dataSource = self;
64 | [self.view addSubview:self.collectionView];
65 |
66 | self.collectionView.frame = CGRectMake(0, 100, self.view.bounds.size.width, itemH);
67 | }
68 |
69 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
70 | {
71 | return self.dataSource.count;
72 | }
73 |
74 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
75 | {
76 | MPCardLayoutCell *cell = (MPCardLayoutCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"MPCardLayoutCell" forIndexPath:indexPath];
77 | ZFTableData *data = self.dataSource[indexPath.row];
78 | [cell.coverImageView setImageWithURLString:data.thumbnail_url placeholderImageName:nil];
79 | return cell;
80 | }
81 |
82 | @end
83 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPDetailViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPDetailViewController.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "MPPlayerController.h"
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MPDetailViewController : UIViewController
14 |
15 | @property (nonatomic, strong) MPPlayerController *player;
16 | @property (nonatomic) NSInteger index;
17 | @property (nonatomic, strong) NSMutableArray *dataSource;
18 | @property (nonatomic, strong) UIView *startView;
19 | @property (nonatomic, strong) UIImage *startImage;
20 | @property (nonatomic, copy) void(^popbackBlock)(void);
21 |
22 | @end
23 |
24 | NS_ASSUME_NONNULL_END
25 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPDetailViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPDetailViewController.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPDetailViewController.h"
10 | #import "ZFDouYinCell.h"
11 | #import "ZFDouYinControlView.h"
12 | #import
13 | #import
14 | #import "MPTransition.h"
15 |
16 | static NSString *kIdentifier = @"kIdentifier";
17 |
18 | @interface MPDetailViewController ()
19 |
20 | @property (nonatomic, strong) UITableView *tableView;
21 | @property (nonatomic, strong) UIButton *backBtn;
22 | @property (nonatomic, strong) ZFDouYinControlView *controlView;
23 | @property (nonatomic, strong) NSIndexPath *playingIndexPath;
24 | @property (nonatomic, strong) ZFPlayerControlView *preControlView;
25 | @property (nonatomic) BOOL isInited;
26 | @property (nonatomic) BOOL isPassPlayer;
27 | @end
28 |
29 | @implementation MPDetailViewController
30 |
31 | - (void)viewDidLoad
32 | {
33 | [super viewDidLoad];
34 | self.view.backgroundColor = [UIColor whiteColor];
35 | [self.view addSubview:self.tableView];
36 | [self.view addSubview:self.backBtn];
37 |
38 | @weakify(self)
39 | if (!self.player) {
40 | self.player = [MPPlayerController playrWithContainerView:[UIView new]];
41 | [self requestData];
42 | self.player.playableArray = self.dataSource;
43 | }else {
44 | for(UIView *subView in self.player.currentPlayerManager.view.subviews) {
45 | [subView removeFromSuperview];
46 | }
47 | self.isPassPlayer = YES;
48 | }
49 |
50 | self.player.presentationSizeChanged = ^(id _Nonnull asset, CGSize size) {
51 | @strongify(self)
52 | if (size.width >= size.height) {
53 | self.player.currentPlayerManager.scalingMode = ZFPlayerScalingModeAspectFit;
54 | } else {
55 | self.player.currentPlayerManager.scalingMode = ZFPlayerScalingModeAspectFill;
56 | }
57 | };
58 | [self.tableView reloadData];
59 | self.playingIndexPath = [NSIndexPath indexPathForRow:self.index inSection:0];
60 | [self.tableView scrollToRowAtIndexPath:self.playingIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
61 | }
62 |
63 | - (void)viewWillLayoutSubviews {
64 | [super viewWillLayoutSubviews];
65 | self.backBtn.frame = CGRectMake(15, CGRectGetMaxY([UIApplication sharedApplication].statusBarFrame), 36, 36);
66 | }
67 |
68 | - (void)viewWillAppear:(BOOL)animated {
69 | [super viewWillAppear:animated];
70 |
71 | [self.navigationController setNavigationBarHidden:YES animated:animated];
72 | }
73 |
74 | - (void)viewWillDisappear:(BOOL)animated {
75 | [super viewWillDisappear:animated];
76 |
77 | [self.navigationController setNavigationBarHidden:NO animated:animated];
78 | }
79 |
80 | - (void)requestData {
81 | NSString *path = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"];
82 | NSData *data = [NSData dataWithContentsOfFile:path];
83 | NSDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
84 |
85 | self.dataSource = @[].mutableCopy;
86 | NSArray *videoList = [rootDict objectForKey:@"list"];
87 | for (NSDictionary *dataDic in videoList) {
88 | ZFTableData *data = [[ZFTableData alloc] init];
89 | [data setValuesForKeysWithDictionary:dataDic];
90 | [self.dataSource addObject:data];
91 | }
92 | self.player.playableArray = self.dataSource;
93 | }
94 |
95 | - (void)viewDidAppear:(BOOL)animated
96 | {
97 | [super viewDidAppear:animated];
98 | if (!self.isInited) {
99 | self.isInited = YES;
100 | if (self.isPassPlayer) {
101 | UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:self.playingIndexPath];
102 | UIView *playerView = [cell viewWithTag:10086];
103 | self.player.controlView = self.controlView;
104 | if (playerView) {
105 | [self.player updateNoramlPlayerWithContainerView:playerView];
106 | [self.controlView resetControlView];
107 | ZFTableData *data = self.dataSource[self.playingIndexPath.row];
108 | UIViewContentMode imageMode;
109 | if (data.thumbnail_width >= data.thumbnail_height) {
110 | imageMode = UIViewContentModeScaleAspectFit;
111 | } else {
112 | imageMode = UIViewContentModeScaleAspectFill;
113 | }
114 | [self.controlView showCoverViewWithUrl:data.thumbnail_url withImageMode:imageMode];
115 | }
116 | }else {
117 | self.player.controlView = self.controlView;
118 | [self playTheVideoAtIndexPath:self.playingIndexPath scrollToTop:NO];
119 | }
120 | }
121 | self.navigationController.delegate = self;
122 | }
123 |
124 | - (void)didMoveToParentViewController:(UIViewController *)parent {
125 | if (!parent) {
126 | if (self.isPassPlayer) {
127 | [self.controlView removeFromSuperview];
128 | for (UIView *subView in self.player.currentPlayerManager.view.subviews) {
129 | [subView removeFromSuperview];
130 | }
131 | self.player.controlView = self.preControlView;
132 | if (self.popbackBlock) {
133 | self.popbackBlock();
134 | }
135 | }
136 | }
137 | }
138 |
139 | #pragma mark - UIScrollViewDelegate 列表播放必须实现
140 | - (void)cellPlayVideo
141 | {
142 | if (self.tableView.visibleCells.count && self.tableView.visibleCells.count == 1) {
143 | UITableViewCell *cell = self.tableView.visibleCells.firstObject;
144 | NSIndexPath *ip = [self.tableView indexPathForCell:cell];
145 | NSComparisonResult result = [ip compare:self.playingIndexPath];
146 | // 判断indexPath是否发生变化
147 | if (ip && result != NSOrderedSame) {
148 | [self.player stop];
149 | [self playTheVideoAtIndexPath:ip scrollToTop:NO];
150 | }
151 | if (ip) {
152 | self.playingIndexPath = ip;
153 | }
154 | }
155 | }
156 |
157 |
158 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
159 | {
160 | [self cellPlayVideo];
161 | }
162 |
163 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView
164 | {
165 | [self handlePlayerOutofScreen: scrollView];
166 | }
167 |
168 | - (void)handlePlayerOutofScreen: (UIScrollView *)scrollView
169 | {
170 | // 为了处理快速滑动时,player复用的bug
171 | UIView *cell = [self.tableView cellForRowAtIndexPath:self.playingIndexPath];
172 | if (!cell && self.playingIndexPath) {
173 | [self.player stop];
174 | return;
175 | }
176 | }
177 |
178 | #pragma mark - UITableViewDataSource
179 |
180 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
181 | return self.dataSource.count;
182 | }
183 |
184 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
185 | ZFDouYinCell *cell = [tableView dequeueReusableCellWithIdentifier:kIdentifier];
186 | cell.data = self.dataSource[indexPath.row];
187 | return cell;
188 | }
189 |
190 | #pragma mark - private method
191 |
192 | - (void)backClick:(UIButton *)sender {
193 | [self.navigationController popViewControllerAnimated:YES];
194 | self.navigationController.delegate = self;
195 | }
196 |
197 | /// play the video
198 | - (void)playTheVideoAtIndexPath:(NSIndexPath *)indexPath scrollToTop:(BOOL)scrollToTop {
199 | UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
200 | UIView *contanier = [cell viewWithTag:10086];
201 | [self.player updateNoramlPlayerWithContainerView:contanier];
202 | [self.player playWithPlayable:self.dataSource[indexPath.row]];
203 | [self.controlView resetControlView];
204 | ZFTableData *data = self.dataSource[indexPath.row];
205 | UIViewContentMode imageMode;
206 | if (data.thumbnail_width >= data.thumbnail_height) {
207 | imageMode = UIViewContentModeScaleAspectFit;
208 | } else {
209 | imageMode = UIViewContentModeScaleAspectFill;
210 | }
211 | [self.controlView showCoverViewWithUrl:data.thumbnail_url withImageMode:imageMode];
212 | self.playingIndexPath = indexPath;
213 | }
214 |
215 | - (UITableView *)tableView {
216 | if (!_tableView) {
217 | _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
218 | _tableView.pagingEnabled = YES;
219 | [_tableView registerClass:[ZFDouYinCell class] forCellReuseIdentifier:kIdentifier];
220 | _tableView.backgroundColor = [UIColor lightGrayColor];
221 | _tableView.delegate = self;
222 | _tableView.dataSource = self;
223 | _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
224 | _tableView.showsVerticalScrollIndicator = NO;
225 | _tableView.scrollsToTop = NO;
226 | if (@available(iOS 11.0, *)) {
227 | _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
228 | } else {
229 | self.automaticallyAdjustsScrollViewInsets = NO;
230 | }
231 | _tableView.estimatedRowHeight = 0;
232 | _tableView.estimatedSectionFooterHeight = 0;
233 | _tableView.estimatedSectionHeaderHeight = 0;
234 | _tableView.frame = self.view.bounds;
235 | _tableView.rowHeight = _tableView.frame.size.height;
236 | _tableView.scrollsToTop = NO;
237 | }
238 | return _tableView;
239 | }
240 |
241 | - (ZFDouYinControlView *)controlView {
242 | if (!_controlView) {
243 | _controlView = [ZFDouYinControlView new];
244 | }
245 | return _controlView;
246 | }
247 |
248 | - (UIButton *)backBtn {
249 | if (!_backBtn) {
250 | _backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
251 | [_backBtn setImage:[UIImage imageNamed:@"icon_titlebar_whiteback"] forState:UIControlStateNormal];
252 | [_backBtn addTarget:self action:@selector(backClick:) forControlEvents:UIControlEventTouchUpInside];
253 | }
254 | return _backBtn;
255 | }
256 |
257 | - (void)setPlayer:(MPPlayerController *)player
258 | {
259 | _player = player;
260 | self.preControlView = (ZFPlayerControlView *)player.controlView;
261 | [self.preControlView removeFromSuperview];
262 | }
263 |
264 | - (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
265 | if (self.startView == nil)
266 | return nil;
267 | if (self.playingIndexPath &&
268 | self.playingIndexPath.row != self.index)
269 | return nil;
270 | return [MPTransition animationWithDuration:0.3 startView:self.startView startImage:self.startImage player:self.player operation:operation completion:^{
271 | }];
272 | }
273 |
274 |
275 | @end
276 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPListViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPListViewController.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MPListViewController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPListViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPListViewController.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPListViewController.h"
10 | #import "ZFTableViewCell.h"
11 | #import "ZFTableData.h"
12 | #import "MPPlayerController.h"
13 | #import
14 | #import "MPDetailViewController.h"
15 | #import "ZFUtilities.h"
16 |
17 | static NSString *kIdentifier = @"kIdentifier";
18 |
19 | @interface MPListViewController ()
20 |
21 | @property (nonatomic, strong) UITableView *tableView;
22 | @property (nonatomic, strong) NSMutableArray *dataSource;
23 | @property (nonatomic, strong) MPPlayerController *player;
24 | @property (nonatomic, strong) ZFPlayerControlView *controlView;
25 | @property (nonatomic, strong) NSMutableArray *playableArray;
26 | @property (nonatomic) BOOL isInited;
27 |
28 | @end
29 |
30 | @implementation MPListViewController
31 |
32 | - (void)viewDidLoad {
33 | [super viewDidLoad];
34 | self.tableView = [[UITableView alloc] init];
35 | self.tableView.delegate = self;
36 | self.tableView.dataSource = self;
37 | CGFloat y = iPhoneX ? 88 : 64;
38 | self.tableView.frame = CGRectMake(0, y, self.view.bounds.size.width, self.view.bounds.size.height - y);
39 | [self.tableView registerClass:[ZFTableViewCell class] forCellReuseIdentifier:kIdentifier];
40 | if (@available(iOS 11.0, *)) {
41 | _tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
42 | } else {
43 | self.automaticallyAdjustsScrollViewInsets = NO;
44 | }
45 | _tableView.estimatedRowHeight = 0;
46 | _tableView.estimatedSectionFooterHeight = 0;
47 | _tableView.estimatedSectionHeaderHeight = 0;
48 | [self.view addSubview:self.tableView];
49 |
50 | self.player = [MPPlayerController playerWithScrollView:self.tableView containerViewTag:100];
51 | self.controlView = [[ZFPlayerControlView alloc] init];
52 | self.player.controlView = self.controlView;
53 | [self setupPlayerDisappearBlock];
54 |
55 | /// 停止的时候找出最合适的播放(只能找到设置了tag值cell)
56 | @weakify(self)
57 | _tableView.zf_scrollViewDidEndScrollingCallback = ^(NSIndexPath * _Nonnull indexPath) {
58 | @strongify(self)
59 | if (!self.player.playingIndexPath) {
60 | [self playTheVideoAtIndexPath:indexPath scrollToTop:NO];
61 | }
62 | };
63 |
64 | [self requestData];
65 | }
66 |
67 | - (void)viewDidAppear:(BOOL)animated {
68 | [super viewDidAppear:animated];
69 | if (!self.isInited) {
70 | self.isInited = YES;
71 | @weakify(self)
72 | [self.tableView zf_filterShouldPlayCellWhileScrolled:^(NSIndexPath *indexPath) {
73 | @strongify(self)
74 | [self playTheVideoAtIndexPath:indexPath scrollToTop:NO];
75 | }];
76 | }
77 |
78 | }
79 |
80 | - (void)setupPlayerDisappearBlock
81 | {
82 | @weakify(self)
83 | self.player.zf_playerDisappearingInScrollView = ^(NSIndexPath * _Nonnull indexPath, CGFloat playerDisapperaPercent) {
84 | @strongify(self)
85 | // 超出需要播放的百分比时,马上记录播放时间,方便下次seek
86 | if (playerDisapperaPercent >= self.player.playerDisapperaPercent) {
87 | ZFTableData *data = self.playableArray[indexPath.row];
88 | data.current_time = self.player.currentPlayerManager.currentTime;
89 | }
90 | };
91 | }
92 |
93 | - (void)requestData {
94 | NSString *path = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"];
95 | NSData *data = [NSData dataWithContentsOfFile:path];
96 | NSDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
97 |
98 | self.dataSource = @[].mutableCopy;
99 | self.playableArray = @[].mutableCopy;
100 | NSArray *videoList = [rootDict objectForKey:@"list"];
101 | for (NSDictionary *dataDic in videoList) {
102 | ZFTableData *data = [[ZFTableData alloc] init];
103 | [data setValuesForKeysWithDictionary:dataDic];
104 | ZFTableViewCellLayout *layout = [[ZFTableViewCellLayout alloc] initWithData:data];
105 | [self.playableArray addObject:data];
106 | [self.dataSource addObject:layout];
107 | }
108 | self.player.playableArray = self.playableArray;
109 | }
110 |
111 | /// play the video
112 | - (void)playTheVideoAtIndexPath:(NSIndexPath *)indexPath scrollToTop:(BOOL)scrollToTop {
113 | NSInteger index = indexPath.row;
114 | ZFTableViewCellLayout *layout = self.dataSource[index];
115 | [self.player playTheIndexPath:indexPath playable:self.playableArray[index]];
116 | // seek播放记录
117 | self.player.currentPlayerManager.seekTime = layout.data.current_time;
118 | [self.controlView showTitle:layout.data.title
119 | coverURLString:layout.data.thumbnail_url
120 | fullScreenMode:layout.isVerticalVideo?ZFFullScreenModePortrait:ZFFullScreenModeLandscape];
121 | }
122 |
123 | #pragma mark - UIScrollViewDelegate 列表播放必须实现
124 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
125 | [scrollView zf_scrollViewDidEndDecelerating];
126 | }
127 |
128 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
129 | [scrollView zf_scrollViewDidEndDraggingWillDecelerate:decelerate];
130 | }
131 |
132 | - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
133 | [scrollView zf_scrollViewDidScrollToTop];
134 | }
135 |
136 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
137 | [scrollView zf_scrollViewDidScroll];
138 | }
139 |
140 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
141 | [scrollView zf_scrollViewWillBeginDragging];
142 | }
143 |
144 | #pragma mark - UITableViewDataSource
145 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
146 | return self.dataSource.count;
147 | }
148 |
149 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
150 | ZFTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kIdentifier];
151 | [cell setDelegate:self withIndexPath:indexPath];
152 | cell.layout = self.dataSource[indexPath.row];
153 | [cell setNormalMode];
154 | return cell;
155 | }
156 |
157 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
158 | ZFTableViewCellLayout *layout = self.dataSource[indexPath.row];
159 | return layout.height;
160 | }
161 |
162 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
163 | {
164 | ZFTableViewCell *cell = (ZFTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
165 | NSIndexPath *currentIndexPath = [self.tableView indexPathForCell:cell];
166 | // 点击的不是正在播放的cell,就先播放再跳转
167 | if ([currentIndexPath compare:self.tableView.zf_playingIndexPath] != NSOrderedSame) {
168 | [self.player stopCurrentPlayingCell];
169 | self.tableView.zf_playingIndexPath = currentIndexPath;
170 | [self playTheVideoAtIndexPath:currentIndexPath scrollToTop:NO];
171 | [self.player.currentPlayerManager.view layoutIfNeeded];
172 | }
173 | self.tableView.zf_playingIndexPath = currentIndexPath;
174 |
175 | MPDetailViewController *vc = [[MPDetailViewController alloc] init];
176 | vc.player = self.player;
177 | vc.index = indexPath.row;
178 | vc.startImage = cell.coverImageView.image;
179 | vc.startView = cell.coverImageView;
180 | vc.dataSource = [self.playableArray mutableCopy];
181 | @weakify(self)
182 | vc.popbackBlock = ^{
183 | @strongify(self)
184 | [self.player updateScrollViewPlayerToCell];
185 | [self.player.currentPlayerManager play];
186 | };
187 | self.navigationController.delegate = vc;
188 | [self.navigationController pushViewController:vc animated:YES];
189 | }
190 |
191 | - (void)zf_playTheVideoAtIndexPath:(NSIndexPath *)indexPath {
192 | [self playTheVideoAtIndexPath:indexPath scrollToTop:NO];
193 | }
194 |
195 |
196 |
197 | @end
198 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPUserDynamicDetailViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPUserDynamicDetailViewController.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/18.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MPUserDynamicDetailViewController : UIViewController
14 |
15 | @property (nonatomic) NSInteger index;
16 | @property (nonatomic) NSInteger totalCount;
17 | @property (nonatomic, strong) UIImage *iconImage;
18 | @property (nonatomic, strong) UIImageView *startImageView;
19 | @property (nonatomic, strong) UICollectionViewCell *startCell;
20 |
21 | @end
22 |
23 | NS_ASSUME_NONNULL_END
24 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPUserDynamicDetailViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPUserDynamicDetailViewController.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/18.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPUserDynamicDetailViewController.h"
10 | #import "MPTransition.h"
11 | #import "MPUserDynamicViewController.h"
12 | #import "MPUserDynamicTransition.h"
13 | #import
14 |
15 | @interface MPUserDynamicDetailViewController ()
16 |
17 | @property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactiveGes;
18 | @property (nonatomic, assign) BOOL isInteracting;
19 | @property (nonatomic, strong) MPUserDynamicTransition *transition;
20 | @property (nonatomic, assign) CGFloat startOffsetY;
21 | @property (nonatomic, assign) CGFloat lastOffsetY;
22 |
23 | @end
24 |
25 | @implementation MPUserDynamicDetailViewController
26 |
27 | - (void)viewDidLoad {
28 | [super viewDidLoad];
29 | self.view.backgroundColor = [UIColor redColor];
30 | self.interactiveGes = [[UIPercentDrivenInteractiveTransition alloc] init];
31 | self.isInteracting = YES;
32 | UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
33 | [self.view addGestureRecognizer:pan];
34 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector(backAction)];
35 | }
36 |
37 | - (void)viewDidAppear:(BOOL)animated
38 | {
39 | [super viewDidAppear:animated];
40 | self.navigationController.delegate = self;
41 | }
42 |
43 | - (void)backAction
44 | {
45 | self.isInteracting = NO;
46 | [self.navigationController popViewControllerAnimated:YES];
47 | }
48 |
49 | - (CGFloat)percentForGesture:(UIPanGestureRecognizer *)gesture{
50 | // 最多只能移动SL_SCREEN_HEIGHT * 0.5
51 | CGFloat maxOffset = ZFPlayer_ScreenHeight * 0.5;
52 | CGFloat y = [gesture locationInView:[UIApplication sharedApplication].keyWindow].y;
53 | // 移动的距离
54 | CGFloat distance = y - self.startOffsetY;
55 | distance = MIN(maxOffset, distance);
56 | double degree = (distance / maxOffset) * M_PI_2;
57 | // 为增量实现一个曲线变化的效果
58 | double x = 1 - (sin(degree));
59 | // 计算增量
60 | CGFloat delta = distance - self.lastOffsetY;
61 | self.lastOffsetY = self.lastOffsetY + x * delta;
62 | self.lastOffsetY = MAX(self.lastOffsetY, 0);
63 | CGFloat percent = self.lastOffsetY / maxOffset;
64 | return percent;
65 | }
66 |
67 |
68 | - (void)panAction: (UIPanGestureRecognizer *)gestureRecognizer
69 | {
70 | switch (gestureRecognizer.state){
71 | case UIGestureRecognizerStateBegan:
72 | {
73 | self.startOffsetY = [gestureRecognizer locationInView:[UIApplication sharedApplication].keyWindow].y;
74 | [self.navigationController popViewControllerAnimated:YES];
75 | break;
76 | }
77 | case UIGestureRecognizerStateChanged:
78 | // 调用updateInteractiveTransition来更新动画进度
79 | // 里面嵌套定义 percentForGesture 方法计算动画进度
80 | [self.interactiveGes updateInteractiveTransition:[self percentForGesture:gestureRecognizer]];
81 | break;
82 | case UIGestureRecognizerStateEnded:
83 | //判断手势位置,要大于一般,就完成这个转场,要小于一半就取消
84 | if ([self percentForGesture:gestureRecognizer] >= 0.4) {
85 | self.transition.isComplete = YES;
86 | // 完成交互转场
87 | [self.interactiveGes finishInteractiveTransition];
88 | }else {
89 | // 取消交互转场
90 | [self.interactiveGes cancelInteractiveTransition];
91 | }
92 | break;
93 | default:
94 | [self.interactiveGes cancelInteractiveTransition];
95 | break;
96 | }
97 | }
98 |
99 | // MARK: - Transition
100 | - (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
101 | UIView *startView = self.startCell;
102 | UIImage *startImage = self.iconImage;
103 | CGFloat endX = 0;
104 | if (operation == UINavigationControllerOperationPop) {
105 | endX = [self getPopTransitionEndX];
106 | }
107 | if (startView == nil)
108 | return nil;
109 | NSTimeInterval duration = 0.2;
110 | MPUserDynamicTransition *transition = [MPUserDynamicTransition animationWithDuration:duration startView:startView startImage:startImage endX:endX operation:operation];
111 | if (operation == UINavigationControllerOperationPop) {
112 | transition.isInteracting = self.isInteracting;
113 | }
114 | self.transition = transition;
115 | return transition;
116 | }
117 |
118 | - (id )navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id)animationController
119 | {
120 | return self.isInteracting ? self.interactiveGes : nil;
121 | }
122 |
123 | - (CGFloat)getPopTransitionEndX
124 | {
125 | CGRect winFrame = [self.startCell convertRect:self.startCell.frame toView:nil];
126 | return winFrame.origin.x;
127 | }
128 |
129 | @end
130 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPUserDynamicViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPUserDynamicViewController.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/18.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MPUserDynamicViewController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPUserDynamicViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPUserDynamicViewController.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/18.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPUserDynamicViewController.h"
10 | #import "MPUserDynamicCollectionViewCell.h"
11 | #import "ZFUtilities.h"
12 | #import "ZFTableData.h"
13 | #import "MPUserDynamicDetailViewController.h"
14 | #import
15 |
16 | @interface MPUserDynamicViewController ()<
17 | UICollectionViewDelegate,
18 | UICollectionViewDataSource
19 | >
20 |
21 | @property (nonatomic, strong) UICollectionView *collectionView;
22 | @property (nonatomic, strong) NSMutableArray *dataSource;
23 | @end
24 |
25 | @implementation MPUserDynamicViewController
26 |
27 | - (void)viewDidLoad {
28 | [super viewDidLoad];
29 | [self requestData];
30 | [self setup];
31 | }
32 |
33 | - (void)requestData
34 | {
35 | NSString *path = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"];
36 | NSData *data = [NSData dataWithContentsOfFile:path];
37 | NSDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
38 |
39 | self.dataSource = @[].mutableCopy;
40 | NSArray *videoList = [rootDict objectForKey:@"list"];
41 | for (NSDictionary *dataDic in videoList) {
42 | ZFTableData *data = [[ZFTableData alloc] init];
43 | [data setValuesForKeysWithDictionary:dataDic];
44 | [self.dataSource addObject:data];
45 | }
46 | }
47 |
48 | - (void)setup
49 | {
50 | self.view.backgroundColor = [UIColor whiteColor];
51 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
52 | layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
53 | layout.itemSize = CGSizeMake(80, 80);
54 | self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
55 |
56 | [self.collectionView registerClass:[MPUserDynamicCollectionViewCell class] forCellWithReuseIdentifier:@"MPUserDynamicCollectionViewCell"];
57 | self.collectionView.showsVerticalScrollIndicator = NO;
58 | self.collectionView.showsHorizontalScrollIndicator = NO;
59 | self.collectionView.backgroundColor = [UIColor whiteColor];
60 | self.collectionView.delegate = self;
61 | self.collectionView.dataSource = self;
62 | [self.view addSubview:self.collectionView];
63 |
64 | self.collectionView.frame = CGRectMake(0, 100, self.view.frame.size.width, 80);
65 | }
66 |
67 | #pragma mark - UICollectionViewDelegate, UICollectionViewDataSource
68 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
69 | {
70 | return self.dataSource.count;
71 | }
72 |
73 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
74 | {
75 | MPUserDynamicCollectionViewCell *cell = (MPUserDynamicCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"MPUserDynamicCollectionViewCell" forIndexPath:indexPath];
76 | ZFTableData *data = self.dataSource[indexPath.row];
77 | [cell.iconImageView setImageWithURLString:data.head placeholder:nil];
78 | return cell;
79 | }
80 |
81 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
82 | {
83 | MPUserDynamicDetailViewController *vc = [[MPUserDynamicDetailViewController alloc] init];
84 | vc.index = indexPath.row;
85 | vc.totalCount = self.dataSource.count;
86 | MPUserDynamicCollectionViewCell *cell = (MPUserDynamicCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
87 | vc.iconImage = cell.iconImageView.image;
88 | vc.startImageView = cell.iconImageView;
89 | vc.startCell = cell;
90 | self.navigationController.delegate = vc;
91 | [self.navigationController pushViewController:vc animated:YES];
92 | }
93 |
94 |
95 | @end
96 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPWaterFallViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPWaterFallViewController.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/1/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MPWaterFallViewController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Controller/MPWaterFallViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPWaterFallViewController.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/1/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPWaterFallViewController.h"
10 | #import "MPWaterFallCollectionViewCell.h"
11 | #import "MPWaterFallLayout.h"
12 | #import "ZFUtilities.h"
13 | #import "ZFTableData.h"
14 | #import "MPDetailViewController.h"
15 |
16 | @interface MPWaterFallViewController ()
17 |
18 | @property (nonatomic, strong) UICollectionView *collectionView;
19 | @property (nonatomic, strong) NSMutableArray *dataSource;
20 | @property (nonatomic, strong) MPWaterFallLayout *layout;
21 |
22 | @end
23 |
24 | @implementation MPWaterFallViewController
25 |
26 | - (void)viewDidLoad {
27 | [super viewDidLoad];
28 | [self setupUI];
29 | }
30 |
31 | - (void)setupUI
32 | {
33 | self.view.backgroundColor = [UIColor whiteColor];
34 |
35 | MPWaterFallLayout *layout = [MPWaterFallLayout waterFallLayoutWithColumn:2];
36 | layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 0);
37 | layout.columnSpacing = 10;
38 | layout.rowSpacing = 10;
39 | layout.dataSource = self;
40 | self.layout = layout;
41 |
42 | CGFloat y = iPhoneX ? 88 : 64;
43 | self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, y, ZFPlayer_ScreenWidth, ZFPlayer_ScreenHeight - y) collectionViewLayout:layout];
44 | self.collectionView.backgroundColor = [UIColor whiteColor];
45 | self.collectionView.delegate = self;
46 | self.collectionView.dataSource = self;
47 | [self.collectionView registerClass:[MPWaterFallCollectionViewCell class] forCellWithReuseIdentifier:@"MPWaterFallCollectionViewCellID"];
48 | if (@available(iOS 11.0, *)) {
49 | self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
50 | } else {
51 | self.automaticallyAdjustsScrollViewInsets = NO;
52 | }
53 | [self.view addSubview:self.collectionView];
54 | [self requestData];
55 | [self.collectionView reloadData];
56 | }
57 |
58 | - (void)requestData {
59 | NSString *path = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"];
60 | NSData *data = [NSData dataWithContentsOfFile:path];
61 | NSDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
62 |
63 | self.dataSource = @[].mutableCopy;
64 | NSArray *videoList = [rootDict objectForKey:@"list"];
65 | for (NSDictionary *dataDic in videoList) {
66 | ZFTableData *data = [[ZFTableData alloc] init];
67 | [data setValuesForKeysWithDictionary:dataDic];
68 | data.thumbnail_height = (self.layout.itemWidth / data.thumbnail_width) * data.thumbnail_height;
69 | [self.dataSource addObject:data];
70 | }
71 | }
72 |
73 |
74 | // MARK: - MPWaterFallLayoutDataSource
75 | - (CGFloat)waterFallLayout:(MPWaterFallLayout *)layout itemHeightForItemWidth:(CGFloat)itemWidth atIndexPath:(NSIndexPath *)indexPath
76 | {
77 | ZFTableData *data = self.dataSource[indexPath.row];
78 | return data.thumbnail_height;
79 | }
80 |
81 | // MARK: - UICollectionViewDelegate, UICollectionViewDataSource
82 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
83 | {
84 | return self.dataSource.count;
85 | }
86 |
87 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
88 | {
89 | MPWaterFallCollectionViewCell *cell = (MPWaterFallCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"MPWaterFallCollectionViewCellID" forIndexPath:indexPath];
90 | ZFTableData *data = self.dataSource[indexPath.row];
91 | cell.data = data;
92 | return cell;
93 | }
94 |
95 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
96 | {
97 | MPWaterFallCollectionViewCell *cell = (MPWaterFallCollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
98 | MPDetailViewController *vc = [[MPDetailViewController alloc] init];
99 | vc.index = indexPath.row;
100 | vc.startImage = cell.imageView.image;
101 | vc.startView = cell.imageView;
102 | vc.dataSource = [self.dataSource mutableCopy];
103 | self.navigationController.delegate = vc;
104 | [self.navigationController pushViewController:vc animated:YES];
105 | }
106 |
107 | @end
108 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSAppTransportSecurity
6 |
7 | NSAllowsArbitraryLoads
8 |
9 |
10 | CFBundleDevelopmentRegion
11 | $(DEVELOPMENT_LANGUAGE)
12 | CFBundleExecutable
13 | $(EXECUTABLE_NAME)
14 | CFBundleIdentifier
15 | $(PRODUCT_BUNDLE_IDENTIFIER)
16 | CFBundleInfoDictionaryVersion
17 | 6.0
18 | CFBundleName
19 | $(PRODUCT_NAME)
20 | CFBundlePackageType
21 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
22 | CFBundleShortVersionString
23 | 1.0
24 | CFBundleVersion
25 | 1
26 | LSRequiresIPhoneOS
27 |
28 | UILaunchStoryboardName
29 | LaunchScreen
30 | UIMainStoryboardFile
31 | Main
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Model/ZFTableData.h:
--------------------------------------------------------------------------------
1 | //
2 | // ZFTableData.h
3 | // ZFPlayer
4 | //
5 | // Created by 紫枫 on 2018/4/24.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "MPPlayableProtocol.h"
11 | #import
12 |
13 | @interface ZFTableData : NSObject
14 | /// 记录播放进度
15 | @property (nonatomic, assign) NSTimeInterval current_time;
16 | @property (nonatomic, copy) NSString *nick_name;
17 | @property (nonatomic, copy) NSString *head;
18 | @property (nonatomic, assign) NSInteger agree_num;
19 | @property (nonatomic, assign) NSInteger share_num;
20 | @property (nonatomic, assign) NSInteger post_num;
21 | @property (nonatomic, copy) NSString *title;
22 | @property (nonatomic, assign) CGFloat thumbnail_width;
23 | @property (nonatomic, assign) CGFloat thumbnail_height;
24 | @property (nonatomic, assign) CGFloat video_duration;
25 | @property (nonatomic, assign) CGFloat video_width;
26 | @property (nonatomic, assign) CGFloat video_height;
27 | @property (nonatomic, copy) NSString *thumbnail_url;
28 | @property (nonatomic, copy) NSString *video_url;
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Model/ZFTableData.m:
--------------------------------------------------------------------------------
1 | //
2 | // ZFTableData.m
3 | // ZFPlayer
4 | //
5 | // Created by 紫枫 on 2018/4/24.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import "ZFTableData.h"
10 |
11 | @implementation ZFTableData
12 |
13 | - (void)setValue:(id)value forUndefinedKey:(NSString *)key {}
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Other/NSString+Size.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSString+Size.h
3 | // createImg
4 | //
5 | // Created by 紫枫 on 2018/4/3.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface NSString (Size)
12 | /**
13 | 根据字体、行数、行间距和指定的宽度constrainedWidth计算文本占据的size
14 | @param font 字体
15 | @param numberOfLines 显示文本行数,值为0不限制行数
16 | @param lineSpacing 行间距
17 | @param constrainedWidth 文本指定的宽度
18 | @return 返回文本占据的size
19 | */
20 | - (CGSize)textSizeWithFont:(UIFont*)font
21 | numberOfLines:(NSInteger)numberOfLines
22 | lineSpacing:(CGFloat)lineSpacing
23 | constrainedWidth:(CGFloat)constrainedWidth;
24 |
25 | /**
26 | 根据字体、行数、行间距和指定的宽度constrainedWidth计算文本占据的size
27 | @param font 字体
28 | @param numberOfLines 显示文本行数,值为0不限制行数
29 | @param constrainedWidth 文本指定的宽度
30 | @return 返回文本占据的size
31 | */
32 | - (CGSize)textSizeWithFont:(UIFont*)font
33 | numberOfLines:(NSInteger)numberOfLines
34 | constrainedWidth:(CGFloat)constrainedWidth;
35 |
36 | /// 计算字符串长度(一行时候)
37 | - (CGSize)textSizeWithFont:(UIFont*)font
38 | limitWidth:(CGFloat)maxWidth;
39 |
40 | - (CGSize)textSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(NSLineBreakMode)lineBreakMode;
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Other/NSString+Size.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSString+Size.m
3 | // createImg
4 | //
5 | // Created by 紫枫 on 2018/4/3.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import "NSString+Size.h"
10 |
11 | @implementation NSString (Size)
12 |
13 | /**
14 | 根据字体、行数、行间距和constrainedWidth计算文本占据的size
15 | **/
16 | - (CGSize)textSizeWithFont:(UIFont*)font
17 | numberOfLines:(NSInteger)numberOfLines
18 | lineSpacing:(CGFloat)lineSpacing
19 | constrainedWidth:(CGFloat)constrainedWidth{
20 |
21 | if (self.length == 0) {
22 | return CGSizeZero;
23 | }
24 | CGFloat oneLineHeight = font.lineHeight;
25 | CGSize textSize = [self boundingRectWithSize:CGSizeMake(constrainedWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:font} context:nil].size;
26 | // 行数
27 | CGFloat rows = textSize.height / oneLineHeight;
28 | CGFloat realHeight = oneLineHeight;
29 | // 0 不限制行数,真实高度加上行间距
30 | if (numberOfLines == 0) {
31 | if (rows >= 1) {
32 | realHeight = (rows * oneLineHeight) + (rows - 1) * lineSpacing;
33 | }
34 | } else {
35 | // 行数超过指定行数的时候,限制行数
36 | if (rows > numberOfLines) {
37 | rows = numberOfLines;
38 | }
39 | realHeight = (rows * oneLineHeight) + (rows - 1) * lineSpacing;
40 | }
41 | // 返回真实的宽高
42 | return CGSizeMake(constrainedWidth, realHeight);
43 | }
44 |
45 | - (CGSize)textSizeWithFont:(UIFont*)font
46 | numberOfLines:(NSInteger)numberOfLines
47 | constrainedWidth:(CGFloat)constrainedWidth{
48 |
49 | if (self.length == 0) {
50 | return CGSizeZero;
51 | }
52 | CGFloat oneLineHeight = font.lineHeight;
53 | CGSize textSize = [self boundingRectWithSize:CGSizeMake(constrainedWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:font} context:nil].size;
54 | // 行数
55 | CGFloat rows = textSize.height / oneLineHeight;
56 | CGFloat realHeight = oneLineHeight;
57 | // 0 不限制行数,真实高度加上行间距
58 | if (numberOfLines == 0) {
59 | if (rows >= 1) {
60 | realHeight = (rows * oneLineHeight) + (rows - 1) ;
61 | }
62 | } else {
63 | // 行数超过指定行数的时候,限制行数
64 | if (rows > numberOfLines) {
65 | rows = numberOfLines;
66 | }
67 | realHeight = (rows * oneLineHeight) + (rows - 1) ;
68 | }
69 | // 返回真实的宽高
70 | return CGSizeMake(constrainedWidth, realHeight);
71 | }
72 |
73 | /// 计算字符串长度(一行时候)
74 | - (CGSize)textSizeWithFont:(UIFont*)font
75 | limitWidth:(CGFloat)maxWidth {
76 | CGSize size = [self boundingRectWithSize:CGSizeMake(MAXFLOAT, 36)options:(NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) attributes:@{ NSFontAttributeName : font} context:nil].size;
77 | size.width = size.width > maxWidth ? maxWidth : size.width;
78 | size.width = ceil(size.width);
79 | size.height = ceil(size.height);
80 | return size;
81 | }
82 |
83 | - (CGSize)textSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(NSLineBreakMode)lineBreakMode {
84 | CGSize textSize;
85 | if (CGSizeEqualToSize(size, CGSizeZero)) {
86 | NSDictionary *attributes = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
87 | textSize = [self sizeWithAttributes:attributes];
88 | } else {
89 | textSize = [self sizeWithFont:font constrainedToSize:size lineBreakMode:NSLineBreakByCharWrapping];
90 | }
91 | return textSize;
92 | }
93 |
94 |
95 | @end
96 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Other/ZFTableViewCellLayout.h:
--------------------------------------------------------------------------------
1 | //
2 | // ZFTableViewCellLayout.h
3 | // ZFPlayer
4 | //
5 | // Created by 紫枫 on 2018/5/22.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "ZFTableData.h"
11 |
12 | @interface ZFTableViewCellLayout : NSObject
13 | @property (nonatomic, strong) ZFTableData *data;
14 | @property (nonatomic, readonly) CGRect headerRect;
15 | @property (nonatomic, readonly) CGRect nickNameRect;
16 | @property (nonatomic, readonly) CGRect videoRect;
17 | @property (nonatomic, readonly) CGRect playBtnRect;
18 | @property (nonatomic, readonly) CGRect titleLabelRect;
19 | @property (nonatomic, readonly) CGRect maskViewRect;
20 | @property (nonatomic, readonly) CGFloat height;
21 | @property (nonatomic, readonly) BOOL isVerticalVideo;
22 |
23 | - (instancetype)initWithData:(ZFTableData *)data;
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Other/ZFTableViewCellLayout.m:
--------------------------------------------------------------------------------
1 | //
2 | // ZFTableViewCellLayout.m
3 | // ZFPlayer
4 | //
5 | // Created by 紫枫 on 2018/5/22.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import "ZFTableViewCellLayout.h"
10 | #import "NSString+Size.h"
11 |
12 | @interface ZFTableViewCellLayout ()
13 |
14 | @property (nonatomic, assign) CGRect headerRect;
15 | @property (nonatomic, assign) CGRect nickNameRect;
16 | @property (nonatomic, assign) CGRect videoRect;
17 | @property (nonatomic, assign) CGRect playBtnRect;
18 | @property (nonatomic, assign) CGRect titleLabelRect;
19 | @property (nonatomic, assign) CGRect maskViewRect;
20 | @property (nonatomic, assign) BOOL isVerticalVideo;
21 | @property (nonatomic, assign) CGFloat height;
22 |
23 | @end
24 |
25 | @implementation ZFTableViewCellLayout
26 |
27 | - (instancetype)initWithData:(ZFTableData *)data {
28 | self = [super init];
29 | if (self) {
30 | _data = data;
31 |
32 | CGFloat min_x = 0;
33 | CGFloat min_y = 0;
34 | CGFloat min_w = 0;
35 | CGFloat min_h = 0;
36 | CGFloat min_view_w = [UIScreen mainScreen].bounds.size.width;
37 | CGFloat margin = 10;
38 |
39 | min_x = 10;
40 | min_y = 10;
41 | min_w = 30;
42 | min_h = min_w;
43 | self.headerRect = CGRectMake(min_x, min_y, min_w, min_h);
44 |
45 | min_x = CGRectGetMaxX(self.headerRect) + 10;
46 | min_y = 18;
47 | min_w = [data.nick_name textSizeWithFont:[UIFont systemFontOfSize:15] limitWidth:min_view_w-2*margin-min_x].width;
48 | min_h = 15;
49 | self.nickNameRect = CGRectMake(min_x, min_y, min_w, min_h);
50 |
51 | min_x = 0;
52 | min_y = CGRectGetMaxY(self.headerRect)+margin;
53 | min_w = min_view_w;
54 | min_h = self.videoHeight;
55 | self.videoRect = CGRectMake(min_x, min_y, min_w, min_h);
56 |
57 | min_w = 44;
58 | min_h = min_w;
59 | min_x = (CGRectGetWidth(self.videoRect)-min_w)/2;
60 | min_y = (CGRectGetHeight(self.videoRect)-min_h)/2;
61 | self.playBtnRect = CGRectMake(min_x, min_y, min_w, min_h);
62 |
63 | min_x = margin;
64 | min_y = CGRectGetMaxY(self.videoRect) + margin;
65 | min_w = CGRectGetWidth(self.videoRect) - 2*margin;
66 | min_h = [data.title textSizeWithFont:[UIFont systemFontOfSize:15] numberOfLines:0 constrainedWidth:min_w].height;
67 | self.titleLabelRect = CGRectMake(min_x, min_y, min_w, min_h);
68 |
69 | self.height = CGRectGetMaxY(self.titleLabelRect)+margin;
70 |
71 | min_x = 0;
72 | min_y = 0;
73 | min_w = min_view_w;
74 | min_h = self.height;
75 | self.maskViewRect = CGRectMake(min_x, min_y, min_w, min_h);
76 |
77 | }
78 | return self;
79 | }
80 |
81 | - (BOOL)isVerticalVideo {
82 | return _data.video_width < _data.video_height;
83 | }
84 |
85 | - (CGFloat)videoHeight {
86 | CGFloat videoHeight;
87 | if (self.isVerticalVideo) {
88 | videoHeight = [UIScreen mainScreen].bounds.size.width * 0.6 * self.data.video_height/self.data.video_width;
89 | } else {
90 | videoHeight = [UIScreen mainScreen].bounds.size.width * self.data.video_height/self.data.video_width;
91 | }
92 | return videoHeight;
93 | }
94 |
95 | @end
96 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Player/MPPlayableProtocol.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef XSTViedoFlowProtocol_h
3 | #define XSTViedoFlowProtocol_h
4 |
5 | /// XSTPlayerController播放的模型,必须实现这个协议
6 | @protocol XSTPlayable
7 | /// string 视频链接
8 | @property (nonatomic, copy) NSString *video_url;
9 | @end
10 |
11 |
12 | #endif /* XSTViedoFlowProtocol_h */
13 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Player/MPPlayerController.h:
--------------------------------------------------------------------------------
1 |
2 |
3 | #import
4 | #import
5 | #import "MPPlayableProtocol.h"
6 |
7 | NS_ASSUME_NONNULL_BEGIN
8 |
9 | @interface MPPlayerController : NSObject
10 |
11 | // 预加载上几条
12 | @property (nonatomic, assign) NSUInteger preLoadNum;
13 | /// 预加载下几条
14 | @property (nonatomic, assign) NSUInteger nextLoadNum;
15 | /// 预加载的的百分比,默认10%
16 | @property (nonatomic, assign) double preloadPrecent;
17 | /// 设置playableAssets后,马上预加载的条数
18 | @property (nonatomic, assign) NSUInteger initPreloadNum;
19 | /// The indexPath is playing.
20 | @property (nonatomic, readonly, nullable) NSIndexPath *playingIndexPath;
21 | /// The current player controller is disappear, not dealloc
22 | @property (nonatomic, getter=isViewControllerDisappear) BOOL viewControllerDisappear;
23 | @property (nonatomic, readonly) UIView *containerView;
24 | /// 可播放的视频的模型数组,若是混合区域,模型需要实现XSTPlayable
25 | /// set之后,先预加载几个
26 | @property (nonatomic, copy) NSArray> *playableArray;
27 | /// 当前正在播放的 MPPlayable 资源
28 | @property (nonatomic, strong, readonly) id currentPlayable;
29 | /// The currentPlayerManager must conform `ZFPlayerMediaPlayback` protocol.
30 | @property (nonatomic, strong) id currentPlayerManager;
31 | /// The custom controlView must conform `ZFPlayerMediaControl` protocol.
32 | @property (nonatomic, strong) UIView *controlView;
33 | /// 保存player在信息流时,应该显示的scalingMode
34 | @property (nonatomic, assign) ZFPlayerScalingMode videoFlowScalingMode;
35 | @property (nonatomic) CGFloat playerDisapperaPercent;
36 | @property (nonatomic) CGFloat playerApperaPercent;
37 | @property (nonatomic, getter=isWWANAutoPlay) BOOL WWANAutoPlay;
38 | @property (nonatomic, assign) BOOL isPlaying;
39 |
40 | // MARK: - Block
41 | /// 准备播放的block
42 | @property (nonatomic, copy, nullable) void(^playerReadyToPlay)(id asset, NSURL *assetURL);
43 | /// 播放进度的block
44 | @property (nonatomic, copy, nullable) void(^playerPlayTimeChanged)(id asset, NSTimeInterval currentTime, NSTimeInterval duration);
45 | /// 播放缓存时间的block
46 | @property (nonatomic, copy, nullable) void(^playerBufferTimeChanged)(id asset, NSTimeInterval bufferTime);
47 | /// 播放失败回调
48 | @property (nonatomic, copy, nullable) void(^playerPlayFailed)(id asset, id error);
49 | /// 播放到结尾的回调
50 | @property (nonatomic, copy, nullable) void(^playerDidToEnd)(id asset);
51 | // 播放器size变化的回调
52 | @property (nonatomic, copy, nullable) void(^presentationSizeChanged)(id asset, CGSize size);
53 | /// The block invoked when the player playback state changed.
54 | @property (nonatomic, copy, nullable) void(^playerPlayStateChanged)(id asset, ZFPlayerPlaybackState playState);
55 | /// The block invoked when the player load state changed.
56 | @property (nonatomic, copy, nullable) void(^playerLoadStateChanged)(id asset, ZFPlayerLoadState loadState);
57 | @property (nonatomic, copy, nullable) void(^zf_playerDisappearingInScrollView)(NSIndexPath *indexPath, CGFloat playerDisapperaPercent);
58 | @property (nonatomic, copy, nullable) void(^zf_playerDidDisappearInScrollView)(NSIndexPath *indexPath);
59 |
60 |
61 | // MARK: - Init
62 | /**
63 | 创建播放单个视频的PlayerController
64 |
65 | @param containerView 指定显示视频的容器
66 | @return MPPlayerController实例
67 | */
68 | + (instancetype)playrWithContainerView:(UIView *)containerView;
69 |
70 | /**
71 | 创建播放单个视频的PlayerController
72 |
73 | @param containerView 指定显示视频的容器
74 | @return MPPlayerController实例
75 | */
76 | - (instancetype)initWithContainerView:(UIView *)containerView;
77 |
78 | /**
79 | 在UITableView或UICollectionView中使用的PlayerController
80 |
81 | @param scrollView UITableView或UICollectionView
82 | @param containerViewTag 指定显示视频的容器tag,cell的子视图的tag
83 | @return PlayerController实例
84 | */
85 | + (instancetype)playerWithScrollView:(UIScrollView *)scrollView containerViewTag:(NSInteger)containerViewTag;
86 |
87 | /**
88 | 在UITableView或UICollectionView中使用的PlayerController
89 |
90 | @param scrollView UITableView或UICollectionView
91 | @param containerViewTag 指定显示视频的容器tag,cell的子视图的tag
92 | @return PlayerController实例
93 | */
94 | - (instancetype)initWithScrollView:(UIScrollView *)scrollView containerViewTag:(NSInteger)containerViewTag;
95 |
96 | // MARK: - Method
97 | /// 移除player,移除其他通知
98 | - (void)stop;
99 |
100 | /// 停止播放播放的Cell
101 | - (void)stopCurrentPlayingCell;
102 |
103 | /// 播放对应的indexPath,传入resouce
104 | - (void)playTheIndexPath:(NSIndexPath *)indexPath playable: (id)playable;
105 |
106 | /// 播放指定的url
107 | - (void)playWithPlayable: (id)playable;
108 |
109 | /// 设置player显示,消失的百分比,用于判断自动播放和暂停
110 | - (void)setDisapperaPercent: (CGFloat)disappearPercent appearPercent: (CGFloat)appearPercent;
111 |
112 | /// 横屏显示
113 | - (void)enterLandscapeFullScreen:(UIInterfaceOrientation)orientation animated:(BOOL)animated;
114 |
115 | /// 退出全屏
116 | - (void)exitFullScreen: (BOOL)isAnimated;
117 |
118 | /// Add Player到Cell上
119 | - (void)updateScrollViewPlayerToCell;
120 |
121 | /// 更新Playerc的容器
122 | - (void)updateNoramlPlayerWithContainerView:(UIView *)containerView;
123 |
124 |
125 |
126 | @end
127 |
128 | NS_ASSUME_NONNULL_END
129 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Player/MPPlayerController.m:
--------------------------------------------------------------------------------
1 | //
2 | // XSTPlayerController.m
3 | // XStarSDK
4 | //
5 | // Created by Beauty-ruanjian on 2019/7/4.
6 | //
7 |
8 | #import "MPPlayerController.h"
9 | #import "MPlayerAttributeManager.h"
10 | #import
11 | #import "MPPreLoaderModel.h"
12 | #import
13 |
14 | @interface MPPlayerController()
15 |
16 | @property (nonatomic, strong) ZFPlayerController *player;
17 | /// 预加载的模型数组
18 | @property (nonatomic, strong) NSMutableArray *preloadArr;
19 | @property (nonatomic, assign) BOOL isAnimating;
20 | @property (nonatomic, assign) UIInterfaceOrientation orientation;
21 |
22 | @end
23 |
24 | @implementation MPPlayerController
25 |
26 | // MARK: - Init
27 | + (instancetype)playrWithContainerView:(UIView *)containerView
28 | {
29 | return [[self alloc] initWithContainerView: containerView];
30 | }
31 |
32 | - (instancetype)initWithContainerView:(UIView *)containerView
33 | {
34 | if (self = [super init])
35 | {
36 | MPlayerAttributeManager *mgr = [[MPlayerAttributeManager alloc] init];
37 | _player = [[ZFPlayerController alloc] initWithPlayerManager:mgr containerView:containerView];
38 | [_player setCustomAudioSession:YES];
39 | [self setup];
40 | }
41 | return self;
42 | }
43 |
44 | + (instancetype)playerWithScrollView:(UIScrollView *)scrollView containerViewTag:(NSInteger)containerViewTag
45 | {
46 | return [[self alloc] initWithScrollView:scrollView containerViewTag:containerViewTag];
47 | }
48 |
49 | - (instancetype)initWithScrollView:(UIScrollView *)scrollView containerViewTag:(NSInteger)containerViewTag
50 | {
51 | if (self = [super init])
52 | {
53 | MPlayerAttributeManager *mgr = [[MPlayerAttributeManager alloc] init];
54 | _player = [[ZFPlayerController alloc] initWithScrollView:scrollView playerManager:mgr containerViewTag:containerViewTag];
55 | _player.disableGestureTypes = ZFPlayerDisableGestureTypesPan;
56 | [_player setCustomAudioSession:YES];
57 | [self setup];
58 | }
59 | return self;
60 | }
61 |
62 | - (instancetype)init
63 | {
64 | if (self = [super init])
65 | {
66 | [self setup];
67 | }
68 | return self;
69 | }
70 |
71 | /// 初始化
72 | - (void)setup
73 | {
74 | _preLoadNum = 2;
75 | _nextLoadNum = 2;
76 | _preloadPrecent = 0.1;
77 | _initPreloadNum = 3;
78 | _player.allowOrentitaionRotation = NO;
79 | _player.playerDisapperaPercent = 0.5;
80 | _player.playerApperaPercent = 0.5;
81 | @weakify(self)
82 | _player.playerDidToEnd = ^(id _Nonnull asset) {
83 | @strongify(self)
84 | [self.player.currentPlayerManager replay];
85 | };
86 | }
87 |
88 | // MARK: - Method
89 | - (void)stop
90 | {
91 | self.player.scrollView.zf_playingIndexPath = nil;
92 | [self.player stop];
93 | }
94 |
95 | - (void)stopCurrentPlayingCell
96 | {
97 | [self.player stopCurrentPlayingCell];
98 | }
99 |
100 | - (void)playTheIndexPath:(NSIndexPath *)indexPath playable: (id)playable
101 | {
102 | // 播放前,先停止所有的预加载任务
103 | [self cancelAllPreload];
104 | _currentPlayable = playable;
105 | [self.player playTheIndexPath:indexPath assetURL:[NSURL URLWithString:playable.video_url] scrollToTop:NO];
106 | __weak typeof(self) weakSelf = self;
107 | self.playerReadyToPlay = ^(id _Nonnull asset, NSURL * _Nonnull assetURL) {
108 | [weakSelf preload: playable];
109 | };
110 | }
111 |
112 | - (void)playWithPlayable: (id)playable
113 | {
114 | _currentPlayable = playable;
115 | self.player.assetURL = [NSURL URLWithString:playable.video_url];
116 | }
117 |
118 | - (void)setDisapperaPercent: (CGFloat)disappearPercent appearPercent: (CGFloat)appearPercent
119 | {
120 | self.player.playerDisapperaPercent = disappearPercent;
121 | self.player.playerApperaPercent = appearPercent;
122 | }
123 |
124 | - (void)enterLandscapeFullScreen:(UIInterfaceOrientation)orientation animated:(BOOL)animated
125 | {
126 | CGFloat cellHeight = iPhoneX ? ZFPlayer_ScreenHeight - 83 : ZFPlayer_ScreenHeight;
127 | if (self.isAnimating) {
128 | return;
129 | }
130 | if (self.orientation == orientation) {
131 | return;
132 | }
133 | if (self.player.currentPlayerManager.playState == ZFPlayerPlayStatePlaying ||self.player.currentPlayerManager.playState == ZFPlayerPlayStatePaused
134 | ) {
135 | self.isAnimating = YES;
136 | }
137 | self.orientation = orientation;
138 | CGFloat rotation = 0;
139 | if (orientation == UIInterfaceOrientationLandscapeLeft) {
140 | rotation = M_PI_2;
141 | }else if (orientation == UIInterfaceOrientationLandscapeRight) {
142 | rotation = M_PI_2 * 3;
143 | }
144 | UIView *presentView = self.player.currentPlayerManager.view;
145 | CGRect landRect = CGRectMake(0, 0, cellHeight, ZFPlayer_ScreenWidth);
146 | [UIView animateWithDuration:0.35 animations:^{
147 | presentView.layer.affineTransform = CGAffineTransformMakeRotation(rotation);
148 | } completion:^(BOOL finished) {
149 | self.isAnimating = NO;
150 | }];
151 | presentView.layer.bounds = landRect;
152 | }
153 |
154 | - (void)exitFullScreen: (BOOL)isAnimated
155 | {
156 | CGFloat cellHeight = iPhoneX ? ZFPlayer_ScreenHeight - 49 : ZFPlayer_ScreenHeight;
157 | if (self.isAnimating) {
158 | return;
159 | }
160 | if (self.orientation == UIInterfaceOrientationPortrait) {
161 | return;
162 | }
163 | CGRect frame = CGRectMake(0, 0, ZFPlayer_ScreenWidth, cellHeight);
164 | UIView *presentView = self.player.currentPlayerManager.view;
165 | if (!isAnimated) {
166 | presentView.layer.affineTransform = CGAffineTransformIdentity;
167 | self.orientation = UIInterfaceOrientationPortrait;
168 | }else {
169 | self.isAnimating = YES;
170 | self.orientation = UIInterfaceOrientationPortrait;
171 | [UIView animateWithDuration:0.35 animations:^{
172 | presentView.layer.affineTransform = CGAffineTransformIdentity;
173 | }completion:^(BOOL finish){
174 | self.isAnimating = NO;
175 | }];
176 | }
177 | self.player.currentPlayerManager.view.layer.bounds = frame;
178 | }
179 |
180 | /// Add Player到Cell上
181 | - (void)updateScrollViewPlayerToCell
182 | {
183 | if (self.player.currentPlayerManager.view &&
184 | self.player.scrollView.zf_playingIndexPath &&
185 | self.player.containerViewTag) {
186 | UIView *cell = [self.player.scrollView zf_getCellForIndexPath:self.player.scrollView.zf_playingIndexPath];
187 | UIView *containerView = [cell viewWithTag:self.player.containerViewTag];
188 | [self updateNoramlPlayerWithContainerView:containerView];
189 | }
190 | }
191 |
192 | /// 更新Playerc的容器
193 | - (void)updateNoramlPlayerWithContainerView:(UIView *)containerView
194 | {
195 | [self.player addPlayerViewToContainerView:containerView];
196 | }
197 |
198 | // MARK: - Preload
199 | /// 根据传入的模型,预加载上几个,下几个的视频
200 | - (void)preload: (id)resource
201 | {
202 | if (self.playableArray.count <= 1)
203 | return;
204 | if (_nextLoadNum == 0 && _preLoadNum == 0)
205 | return;
206 | NSInteger start = [self.playableArray indexOfObject:resource];
207 | if (start == NSNotFound)
208 | return;
209 | [self cancelAllPreload];
210 | NSInteger index = 0;
211 | for (NSInteger i = start + 1; i < self.playableArray.count && index < _nextLoadNum; i++)
212 | {
213 | index += 1;
214 | id model = self.playableArray[i];
215 | MPPreLoaderModel *preModel = [self getPreloadModel: model.video_url];
216 | if (preModel) {
217 | @synchronized (self.preloadArr) {
218 | [self.preloadArr addObject: preModel];
219 | }
220 | }
221 | }
222 | index = 0;
223 | for (NSInteger i = start - 1; i >= 0 && index < _preLoadNum; i--)
224 | {
225 | index += 1;
226 | id model = self.playableArray[i];
227 | MPPreLoaderModel *preModel = [self getPreloadModel: model.video_url];
228 | if (preModel) {
229 | @synchronized (self.preloadArr) {
230 | [self.preloadArr addObject:preModel];
231 | }
232 | }
233 | }
234 | [self processLoader];
235 | }
236 |
237 | /// 取消所有的预加载
238 | - (void)cancelAllPreload
239 | {
240 | @synchronized (self.preloadArr) {
241 | if (self.preloadArr.count == 0)
242 | {
243 | return;
244 | }
245 | [self.preloadArr enumerateObjectsUsingBlock:^(MPPreLoaderModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
246 | [obj.loader close];
247 | }];
248 | [self.preloadArr removeAllObjects];
249 | }
250 | }
251 |
252 | - (MPPreLoaderModel *)getPreloadModel: (NSString *)urlStr
253 | {
254 | if (!urlStr)
255 | return nil;
256 | // 判断是否已在队列中
257 | __block Boolean res = NO;
258 | @synchronized (self.preloadArr) {
259 | [self.preloadArr enumerateObjectsUsingBlock:^(MPPreLoaderModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
260 | if ([obj.url isEqualToString:urlStr])
261 | {
262 | res = YES;
263 | *stop = YES;
264 | }
265 | }];
266 | }
267 | if (res)
268 | return nil;
269 | NSURL *proxyUrl = [KTVHTTPCache proxyURLWithOriginalURL: [NSURL URLWithString:urlStr]];
270 | KTVHCDataCacheItem *item = [KTVHTTPCache cacheCacheItemWithURL:proxyUrl];
271 | double cachePrecent = 1.0 * item.cacheLength / item.totalLength;
272 | // 判断缓存已经超过10%了
273 | if (cachePrecent >= self.preloadPrecent)
274 | return nil;
275 | KTVHCDataRequest *req = [[KTVHCDataRequest alloc] initWithURL:proxyUrl headers:[NSDictionary dictionary]];
276 | KTVHCDataLoader *loader = [KTVHTTPCache cacheLoaderWithRequest:req];
277 | MPPreLoaderModel *preModel = [[MPPreLoaderModel alloc] initWithURL:urlStr loader:loader];
278 | return preModel;
279 | }
280 |
281 | - (void)processLoader
282 | {
283 | @synchronized (self.preloadArr) {
284 | if (self.preloadArr.count == 0)
285 | return;
286 | MPPreLoaderModel *model = self.preloadArr.firstObject;
287 | model.loader.delegate = self;
288 | [model.loader prepare];
289 | }
290 | }
291 |
292 | /// 根据loader,移除预加载任务
293 | - (void)removePreloadTask: (KTVHCDataLoader *)loader
294 | {
295 | @synchronized (self.preloadArr) {
296 | MPPreLoaderModel *target = nil;
297 | for (MPPreLoaderModel *model in self.preloadArr) {
298 | if ([model.loader isEqual:loader])
299 | {
300 | target = model;
301 | break;
302 | }
303 | }
304 | if (target)
305 | [self.preloadArr removeObject:target];
306 | }
307 | }
308 |
309 | // MARK: - KTVHCDataLoaderDelegate
310 | - (void)ktv_loaderDidFinish:(KTVHCDataLoader *)loader
311 | {
312 | }
313 | - (void)ktv_loader:(KTVHCDataLoader *)loader didFailWithError:(NSError *)error
314 | {
315 | // 若预加载失败的话,就直接移除任务,开始下一个预加载任务
316 | [self removePreloadTask:loader];
317 | [self processLoader];
318 | }
319 | - (void)ktv_loader:(KTVHCDataLoader *)loader didChangeProgress:(double)progress
320 | {
321 | if (progress >= self.preloadPrecent)
322 | {
323 | [loader close];
324 | [self removePreloadTask:loader];
325 | [self processLoader];
326 | }
327 | }
328 |
329 | // MARK: - Getter
330 | - (BOOL)isViewControllerDisappear
331 | {
332 | return self.player.isViewControllerDisappear;
333 | }
334 |
335 | - (NSIndexPath *)playingIndexPath
336 | {
337 | return self.player.playingIndexPath;
338 | }
339 |
340 | - (NSMutableArray *)preloadArr
341 | {
342 | if (_preloadArr == nil)
343 | {
344 | _preloadArr = [NSMutableArray array];
345 | }
346 | return _preloadArr;
347 | }
348 |
349 | - (id)currentPlayerManager
350 | {
351 | return self.player.currentPlayerManager;
352 | }
353 |
354 | - (BOOL)isPlaying
355 | {
356 | return self.player.currentPlayerManager.isPlaying;
357 | }
358 |
359 | - (UIView *)containerView
360 | {
361 | return self.player.containerView;
362 | }
363 |
364 | - (BOOL)isWWANAutoPlay
365 | {
366 | return self.player.isWWANAutoPlay;
367 | }
368 |
369 | - (void (^)(id _Nonnull, NSTimeInterval, NSTimeInterval))playerPlayTimeChanged
370 | {
371 | return _player.playerPlayTimeChanged;
372 | }
373 |
374 | - (CGFloat)playerApperaPercent
375 | {
376 | return _player.playerApperaPercent;
377 | }
378 |
379 | - (CGFloat)playerDisapperaPercent
380 | {
381 | return _player.playerDisapperaPercent;
382 | }
383 |
384 |
385 | // MARK: - Setter
386 | - (void)setPlayableArray:(NSArray> *)playableArray
387 | {
388 | _playableArray = playableArray;
389 | [self cancelAllPreload];
390 | // 默认预加载前几条数据
391 | NSRange range = NSMakeRange(0, _initPreloadNum);
392 | if (range.length > playableArray.count) {
393 | range.length = playableArray.count;
394 | }
395 | NSArray *subArr = [playableArray subarrayWithRange: range];
396 | for (id model in subArr)
397 | {
398 | MPPreLoaderModel *preload = [self getPreloadModel:model.video_url];
399 | if (preload) {
400 | @synchronized (self.preloadArr) {
401 | [self.preloadArr addObject: preload];
402 | }
403 | }
404 | }
405 | [self processLoader];
406 | }
407 |
408 | - (void)setWWANAutoPlay:(BOOL)WWANAutoPlay
409 | {
410 | self.player.WWANAutoPlay = WWANAutoPlay;
411 | }
412 |
413 | - (void)setControlView:(UIView *)controlView
414 | {
415 | _controlView = controlView;
416 | self.player.controlView = controlView;
417 | }
418 |
419 | - (void)setViewControllerDisappear:(BOOL)viewControllerDisappear
420 | {
421 | self.player.viewControllerDisappear = viewControllerDisappear;
422 | }
423 |
424 | - (void)setPlayingIndexPath:(NSIndexPath * _Nullable)playingIndexPath
425 | {
426 | self.playingIndexPath = playingIndexPath;
427 | }
428 |
429 | - (void)setPlayerDidToEnd:(void (^)(id _Nonnull))playerDidToEnd {
430 | _player.playerDidToEnd = ^(id _Nonnull asset) {
431 | playerDidToEnd(asset);
432 | };
433 | }
434 |
435 | - (void)setPlayerPlayFailed:(void (^)(id _Nonnull, id _Nonnull))playerPlayFailed {
436 | _player.playerPlayFailed = playerPlayFailed;
437 | }
438 |
439 | - (void)setPlayerReadyToPlay:(void (^)(id _Nonnull, NSURL * _Nonnull))playerReadyToPlay {
440 | _player.playerReadyToPlay = ^(id _Nonnull asset, NSURL * _Nonnull assetURL) {
441 | playerReadyToPlay(asset, assetURL);
442 | };
443 | }
444 |
445 | - (void)setPlayerPlayTimeChanged:(void (^)(id _Nonnull, NSTimeInterval, NSTimeInterval))playerPlayTimeChanged {
446 | _player.playerPlayTimeChanged = playerPlayTimeChanged;
447 | }
448 |
449 | - (void)setPlayerBufferTimeChanged:(void (^)(id _Nonnull, NSTimeInterval))playerBufferTimeChanged {
450 | _player.playerBufferTimeChanged = playerBufferTimeChanged;
451 | }
452 |
453 | - (void)setPresentationSizeChanged:(void (^)(id _Nonnull, CGSize))presentationSizeChanged {
454 | _player.presentationSizeChanged = presentationSizeChanged;
455 | }
456 |
457 | - (void)setPlayerPlayStateChanged:(void (^)(id _Nonnull, ZFPlayerPlaybackState))playerPlayStateChanged
458 | {
459 | _player.playerPlayStateChanged = playerPlayStateChanged;
460 | }
461 |
462 | - (void)setPlayerLoadStateChanged:(void (^)(id _Nonnull, ZFPlayerLoadState))playerLoadStateChanged
463 | {
464 | _player.playerLoadStateChanged = playerLoadStateChanged;
465 | }
466 |
467 | - (void)setZf_playerDisappearingInScrollView:(void (^)(NSIndexPath * _Nonnull, CGFloat))zf_playerDisappearingInScrollView
468 | {
469 | _player.zf_playerDisappearingInScrollView = zf_playerDisappearingInScrollView;
470 | }
471 |
472 | - (void)setZf_playerDidDisappearInScrollView:(void (^)(NSIndexPath * _Nonnull))zf_playerDidDisappearInScrollView
473 | {
474 | _player.zf_playerDidDisappearInScrollView = zf_playerDidDisappearInScrollView;
475 | }
476 |
477 | - (void)setPlayerApperaPercent:(CGFloat)playerApperaPercent
478 | {
479 | _player.playerApperaPercent = playerApperaPercent;
480 | }
481 |
482 | - (void)setPlayerDisapperaPercent:(CGFloat)playerDisapperaPercent
483 | {
484 | _player.playerDisapperaPercent = playerDisapperaPercent;
485 | }
486 |
487 | @end
488 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Player/MPPreLoaderModel.h:
--------------------------------------------------------------------------------
1 | //
2 | // PreLoaderModel.h
3 | // ListDemo
4 | //
5 | // Created by Beauty-ruanjian on 2019/4/17.
6 | // Copyright © 2019 Beauty-ruanjian. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | /// 预加载模型
14 | @interface MPPreLoaderModel : NSObject
15 |
16 | /// 加载的URL
17 | @property (nonatomic, copy, readonly) NSString *url;
18 | /// 请求URL的Loader
19 | @property (nonatomic, strong, readonly) KTVHCDataLoader *loader;
20 |
21 | - (instancetype)initWithURL: (NSString *)url loader: (KTVHCDataLoader *)loader;
22 |
23 | @end
24 |
25 | NS_ASSUME_NONNULL_END
26 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Player/MPPreLoaderModel.m:
--------------------------------------------------------------------------------
1 | //
2 | // PreLoaderModel.m
3 | // ListDemo
4 | //
5 | // Created by Beauty-ruanjian on 2019/4/17.
6 | // Copyright © 2019 Beauty-ruanjian. All rights reserved.
7 | //
8 |
9 | #import "MPPreLoaderModel.h"
10 |
11 | @implementation MPPreLoaderModel
12 |
13 | - (instancetype)initWithURL: (NSString *)url loader: (KTVHCDataLoader *)loader
14 | {
15 | if (self = [super init])
16 | {
17 | _url = url;
18 | _loader = loader;
19 | }
20 | return self;
21 | }
22 |
23 |
24 | @end
25 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Player/MPlayerAttributeManager.h:
--------------------------------------------------------------------------------
1 |
2 |
3 | #import
4 | #import
5 |
6 | NS_ASSUME_NONNULL_BEGIN
7 |
8 | /// 视频属性,视频资源等管理
9 | @interface MPlayerAttributeManager : NSObject
10 |
11 | @property (nonatomic) BOOL shouldAutoPlay;
12 |
13 | @end
14 |
15 | NS_ASSUME_NONNULL_END
16 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Player/MPlayerAttributeManager.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MPlayerAttributeManager.h"
3 | #import
4 | #import
5 | #import
6 | #import
7 |
8 | /*!
9 | * Refresh interval for timed observations of AVPlayer
10 | */
11 | static float const kTimeRefreshInterval = 0.1;
12 | static NSString *const kStatus = @"status";
13 | static NSString *const kLoadedTimeRanges = @"loadedTimeRanges";
14 | static NSString *const kPlaybackBufferEmpty = @"playbackBufferEmpty";
15 | static NSString *const kPlaybackLikelyToKeepUp = @"playbackLikelyToKeepUp";
16 | static NSString *const kPresentationSize = @"presentationSize";
17 |
18 | @interface ZFPlayerPresentView : ZFPlayerView
19 |
20 | @property (nonatomic, strong) AVPlayer *player;
21 | /// default is AVLayerVideoGravityResizeAspect.
22 | @property (nonatomic, strong) AVLayerVideoGravity videoGravity;
23 |
24 | @end
25 |
26 | @implementation ZFPlayerPresentView
27 |
28 | + (Class)layerClass {
29 | return [AVPlayerLayer class];
30 | }
31 |
32 | - (AVPlayerLayer *)avLayer {
33 | return (AVPlayerLayer *)self.layer;
34 | }
35 |
36 | - (instancetype)initWithFrame:(CGRect)frame {
37 | self = [super initWithFrame:frame];
38 | if (self) {
39 | self.backgroundColor = [UIColor blackColor];
40 | }
41 | return self;
42 | }
43 |
44 | - (void)setPlayer:(AVPlayer *)player {
45 | if (player == _player) return;
46 | self.avLayer.player = player;
47 | }
48 |
49 | - (void)setVideoGravity:(AVLayerVideoGravity)videoGravity {
50 | if (videoGravity == self.videoGravity) return;
51 | [self avLayer].videoGravity = videoGravity;
52 | }
53 |
54 | - (AVLayerVideoGravity)videoGravity {
55 | return [self avLayer].videoGravity;
56 | }
57 |
58 | @end
59 |
60 | @interface MPlayerAttributeManager () {
61 | id _timeObserver;
62 | id _itemEndObserver;
63 | ZFKVOController *_playerItemKVO;
64 | }
65 |
66 | @property (nonatomic, strong, readonly) AVURLAsset *asset;
67 | @property (nonatomic, strong, readonly) AVPlayerItem *playerItem;
68 | @property (nonatomic, strong, readonly) AVPlayer *player;
69 | @property (nonatomic, strong) AVPlayerLayer *playerLayer;
70 | @property (nonatomic, assign) BOOL isBuffering;
71 | @property (nonatomic, assign) BOOL isReadyToPlay;
72 | @property (nonatomic, copy) void(^muteStateBlock)(void);
73 | /// 记录每个url的加载失败次数的字典,最多重试3次
74 | @property (nonatomic, strong) NSMutableDictionary *retryDic;
75 | /// 记录可以播放的视频后缀
76 | @property (nonatomic, copy) NSArray *playableSuffixArray;
77 |
78 | @end
79 |
80 | @implementation MPlayerAttributeManager
81 |
82 | @synthesize view = _view;
83 | @synthesize currentTime = _currentTime;
84 | @synthesize totalTime = _totalTime;
85 | @synthesize playerPlayTimeChanged = _playerPlayTimeChanged;
86 | @synthesize playerBufferTimeChanged = _playerBufferTimeChanged;
87 | @synthesize playerDidToEnd = _playerDidToEnd;
88 | @synthesize bufferTime = _bufferTime;
89 | @synthesize playState = _playState;
90 | @synthesize loadState = _loadState;
91 | @synthesize assetURL = _assetURL;
92 | @synthesize playerPrepareToPlay = _playerPrepareToPlay;
93 | @synthesize playerReadyToPlay = _playerReadyToPlay;
94 | @synthesize playerPlayStateChanged = _playerPlayStateChanged;
95 | @synthesize playerLoadStateChanged = _playerLoadStateChanged;
96 | @synthesize seekTime = _seekTime;
97 | @synthesize muted = _muted;
98 | @synthesize volume = _volume;
99 | @synthesize presentationSize = _presentationSize;
100 | @synthesize isPlaying = _isPlaying;
101 | @synthesize rate = _rate;
102 | @synthesize isPreparedToPlay = _isPreparedToPlay;
103 | @synthesize scalingMode = _scalingMode;
104 | @synthesize playerPlayFailed = _playerPlayFailed;
105 | @synthesize presentationSizeChanged = _presentationSizeChanged;
106 |
107 | + (void)initialize
108 | {
109 | [KTVHTTPCache logSetConsoleLogEnable:NO];
110 | NSError *error = nil;
111 | [KTVHTTPCache proxyStart:&error];
112 | if (error) {
113 | NSLog(@"Proxy Start Failure, %@", error);
114 | }
115 | [KTVHTTPCache encodeSetURLConverter:^NSURL *(NSURL *URL) {
116 | // NSLog(@"URL Filter reviced URL : %@", URL);
117 | return URL;
118 | }];
119 | [KTVHTTPCache downloadSetUnacceptableContentTypeDisposer:^BOOL(NSURL *URL, NSString *contentType) {
120 | return NO;
121 | }];
122 | // 设置缓存最大容量
123 | [KTVHTTPCache cacheSetMaxCacheLength:1024 * 1024 * 1024];
124 | }
125 |
126 | - (instancetype)init {
127 | self = [super init];
128 | if (self) {
129 | _scalingMode = ZFPlayerScalingModeAspectFit;
130 | self.retryDic = [NSMutableDictionary dictionary];
131 | }
132 | return self;
133 | }
134 |
135 | - (void)setupMuteStateBlock:(void (^)(void))muteStateBlock
136 | {
137 | self.muteStateBlock = muteStateBlock;
138 | }
139 |
140 | - (void)removeMuteStateBlock
141 | {
142 | self.muteStateBlock = nil;
143 | }
144 |
145 | - (void)prepareToPlay {
146 | if (!_assetURL) return;
147 | _isPreparedToPlay = YES;
148 | [self initializePlayer];
149 | [self play];
150 | self.loadState = ZFPlayerLoadStatePrepare;
151 | if (_playerPrepareToPlay) _playerPrepareToPlay(self, self.assetURL);
152 | }
153 |
154 | - (void)reloadPlayer {
155 | self.seekTime = self.currentTime;
156 | [self prepareToPlay];
157 | }
158 |
159 | - (void)play {
160 | if (!_isPreparedToPlay) {
161 | [self prepareToPlay];
162 | } else {
163 | if (self.muteStateBlock) {
164 | self.muteStateBlock();
165 | }
166 | [self.player play];
167 | self.player.rate = self.rate;
168 | self->_isPlaying = YES;
169 | self.playState = ZFPlayerPlayStatePlaying;
170 | }
171 | }
172 |
173 | - (void)pause {
174 | [self.player pause];
175 | self->_isPlaying = NO;
176 | self.playState = ZFPlayerPlayStatePaused;
177 | [_playerItem cancelPendingSeeks];
178 | [_asset cancelLoading];
179 | }
180 |
181 | - (void)stop {
182 | [_playerItemKVO safelyRemoveAllObservers];
183 | self.loadState = ZFPlayerLoadStateUnknown;
184 | if (self.player.rate != 0) [self.player pause];
185 | [self.player removeTimeObserver:_timeObserver];
186 | [self.player replaceCurrentItemWithPlayerItem:nil];
187 | _timeObserver = nil;
188 | [[NSNotificationCenter defaultCenter] removeObserver:_itemEndObserver name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];
189 | _itemEndObserver = nil;
190 | _isPlaying = NO;
191 | _player = nil;
192 | _assetURL = nil;
193 | _playerItem = nil;
194 | _isPreparedToPlay = NO;
195 | self->_currentTime = 0;
196 | self->_totalTime = 0;
197 | self->_bufferTime = 0;
198 | self.isReadyToPlay = NO;
199 | self.playState = ZFPlayerPlayStatePlayStopped;
200 | }
201 |
202 | - (void)replay {
203 | @weakify(self)
204 | [self seekToTime:0 completionHandler:^(BOOL finished) {
205 | @strongify(self)
206 | [self play];
207 | }];
208 | }
209 |
210 | /// Replace the current playback address
211 | - (void)replaceCurrentAssetURL:(NSURL *)assetURL {
212 | self.assetURL = assetURL;
213 | }
214 |
215 | - (void)seekToTime:(NSTimeInterval)time completionHandler:(void (^ __nullable)(BOOL finished))completionHandler {
216 | CMTime seekTime = CMTimeMake(time, 1);
217 | [_player seekToTime:seekTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler];
218 | }
219 |
220 | - (UIImage *)thumbnailImageAtCurrentTime {
221 | AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:_asset];
222 | CMTime expectedTime = _playerItem.currentTime;
223 | CGImageRef cgImage = NULL;
224 |
225 | imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
226 | imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
227 | cgImage = [imageGenerator copyCGImageAtTime:expectedTime actualTime:NULL error:NULL];
228 |
229 | if (!cgImage) {
230 | imageGenerator.requestedTimeToleranceBefore = kCMTimePositiveInfinity;
231 | imageGenerator.requestedTimeToleranceAfter = kCMTimePositiveInfinity;
232 | cgImage = [imageGenerator copyCGImageAtTime:expectedTime actualTime:NULL error:NULL];
233 | }
234 |
235 | UIImage *image = [UIImage imageWithCGImage:cgImage];
236 | return image;
237 | }
238 |
239 | - (BOOL)isPlayableSuffix: (NSString *)suffix
240 | {
241 | if (!suffix || suffix.length == 0)
242 | return NO;
243 | suffix = [suffix uppercaseString];
244 | return [self.playableSuffixArray containsObject:suffix];
245 | }
246 |
247 | #pragma mark - private method
248 |
249 | /// Calculate buffer progress
250 | - (NSTimeInterval)availableDuration {
251 | NSArray *timeRangeArray = _playerItem.loadedTimeRanges;
252 | CMTime currentTime = [_player currentTime];
253 | BOOL foundRange = NO;
254 | CMTimeRange aTimeRange = {0};
255 | if (timeRangeArray.count) {
256 | aTimeRange = [[timeRangeArray objectAtIndex:0] CMTimeRangeValue];
257 | if (CMTimeRangeContainsTime(aTimeRange, currentTime)) {
258 | foundRange = YES;
259 | }
260 | }
261 |
262 | if (foundRange) {
263 | CMTime maxTime = CMTimeRangeGetEnd(aTimeRange);
264 | NSTimeInterval playableDuration = CMTimeGetSeconds(maxTime);
265 | if (playableDuration > 0) {
266 | return playableDuration;
267 | }
268 | }
269 | return 0;
270 | }
271 |
272 | - (void)initializePlayer {
273 | _asset = [AVURLAsset assetWithURL:self.assetURL];
274 | _playerItem = [AVPlayerItem playerItemWithAsset:self.asset];
275 | _player = [AVPlayer playerWithPlayerItem:_playerItem];
276 | [self enableAudioTracks:YES inPlayerItem:_playerItem];
277 |
278 | ZFPlayerPresentView *presentView = (ZFPlayerPresentView *)self.view;
279 | presentView.player = _player;
280 | self.scalingMode = _scalingMode;
281 | if (@available(iOS 9.0, *)) {
282 | _playerItem.canUseNetworkResourcesForLiveStreamingWhilePaused = NO;
283 | }
284 | if (@available(iOS 10.0, *)) {
285 | _playerItem.preferredForwardBufferDuration = 5;
286 | _player.automaticallyWaitsToMinimizeStalling = NO;
287 | }
288 | [self itemObserving];
289 | }
290 |
291 | /// Playback speed switching method
292 | - (void)enableAudioTracks:(BOOL)enable inPlayerItem:(AVPlayerItem*)playerItem {
293 | for (AVPlayerItemTrack *track in playerItem.tracks){
294 | if ([track.assetTrack.mediaType isEqual:AVMediaTypeVideo]) {
295 | track.enabled = enable;
296 | }
297 | }
298 | }
299 |
300 | /**
301 | * 缓冲较差时候回调这里
302 | */
303 | - (void)bufferingSomeSecond {
304 | // playbackBufferEmpty会反复进入,因此在bufferingOneSecond延时播放执行完之前再调用bufferingSomeSecond都忽略
305 | if (self.isBuffering || self.playState == ZFPlayerPlayStatePlayStopped) return;
306 | self.isBuffering = YES;
307 |
308 | // 需要先暂停一小会之后再播放,否则网络状况不好的时候时间在走,声音播放不出来
309 | [self.player pause];
310 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
311 | // 如果此时用户已经暂停了,则不再需要开启播放了
312 | if (!self.isPlaying) {
313 | self.isBuffering = NO;
314 | return;
315 | }
316 | [self play];
317 | // 如果执行了play还是没有播放则说明还没有缓存好,则再次缓存一段时间
318 | self.isBuffering = NO;
319 | if (!self.playerItem.isPlaybackLikelyToKeepUp) [self bufferingSomeSecond];
320 | });
321 | }
322 |
323 | - (void)itemObserving {
324 | [_playerItemKVO safelyRemoveAllObservers];
325 | _playerItemKVO = [[ZFKVOController alloc] initWithTarget:_playerItem];
326 | [_playerItemKVO safelyAddObserver:self
327 | forKeyPath:kStatus
328 | options:NSKeyValueObservingOptionNew
329 | context:nil];
330 | [_playerItemKVO safelyAddObserver:self
331 | forKeyPath:kPlaybackBufferEmpty
332 | options:NSKeyValueObservingOptionNew
333 | context:nil];
334 | [_playerItemKVO safelyAddObserver:self
335 | forKeyPath:kPlaybackLikelyToKeepUp
336 | options:NSKeyValueObservingOptionNew
337 | context:nil];
338 | [_playerItemKVO safelyAddObserver:self
339 | forKeyPath:kLoadedTimeRanges
340 | options:NSKeyValueObservingOptionNew
341 | context:nil];
342 | [_playerItemKVO safelyAddObserver:self
343 | forKeyPath:kPresentationSize
344 | options:NSKeyValueObservingOptionNew
345 | context:nil];
346 |
347 | CMTime interval = CMTimeMakeWithSeconds(kTimeRefreshInterval, NSEC_PER_SEC);
348 | @weakify(self)
349 | _timeObserver = [self.player addPeriodicTimeObserverForInterval:interval queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
350 | @strongify(self)
351 | if (!self) return;
352 | NSArray *loadedRanges = self.playerItem.seekableTimeRanges;
353 | /// 大于0才把状态改为可以播放,解决黑屏问题
354 | if (CMTimeGetSeconds(time) > 0 && !self.isReadyToPlay) {
355 | self.isReadyToPlay = YES;
356 | self.loadState = ZFPlayerLoadStatePlaythroughOK;
357 | }
358 | if (loadedRanges.count > 0) {
359 | if (self.playerPlayTimeChanged) self.playerPlayTimeChanged(self, self.currentTime, self.totalTime);
360 | }
361 | }];
362 |
363 | _itemEndObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
364 | @strongify(self)
365 | if (!self) return;
366 | self.playState = ZFPlayerPlayStatePlayStopped;
367 | if (self.playerDidToEnd) self.playerDidToEnd(self);
368 | }];
369 | }
370 |
371 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
372 | dispatch_async(dispatch_get_main_queue(), ^{
373 | if ([keyPath isEqualToString:kStatus]) {
374 | if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
375 | /// 第一次初始化
376 | if (self.loadState == ZFPlayerLoadStatePrepare) {
377 | if (self.playerPrepareToPlay) self.playerReadyToPlay(self, self.assetURL);
378 | }
379 | if (self.seekTime) {
380 | [self seekToTime:self.seekTime completionHandler:nil];
381 | self.seekTime = 0;
382 | }
383 | if (self.isPlaying) [self play];
384 | self.player.muted = self.muted;
385 | NSArray *loadedRanges = self.playerItem.seekableTimeRanges;
386 | if (loadedRanges.count > 0) {
387 | /// Fix https://github.com/renzifeng/ZFPlayer/issues/475
388 | if (self.playerPlayTimeChanged) self.playerPlayTimeChanged(self, self.currentTime, self.totalTime);
389 | }
390 | } else if (self.player.currentItem.status == AVPlayerItemStatusFailed) {
391 | NSUInteger retryCount = 1;
392 | if (!self.retryDic[self.assetURL.absoluteString]) {
393 | self.retryDic[self.assetURL.absoluteString] = @(retryCount);
394 | }else {
395 | retryCount = [self.retryDic[self.assetURL.absoluteString] intValue];
396 | if (retryCount <= 3) {
397 | retryCount += 1;
398 | self.retryDic[self.assetURL.absoluteString] = @(retryCount);
399 | }
400 | }
401 | if (retryCount <= 3) {
402 | [self reloadPlayer];
403 | }
404 | self.playState = ZFPlayerPlayStatePlayFailed;
405 | NSError *error = self.player.currentItem.error;
406 | if (self.playerPlayFailed) self.playerPlayFailed(self, error);
407 | }
408 | } else if ([keyPath isEqualToString:kPlaybackBufferEmpty]) {
409 | // When the buffer is empty
410 | if (self.playerItem.playbackBufferEmpty) {
411 | self.loadState = ZFPlayerLoadStateStalled;
412 | [self bufferingSomeSecond];
413 | }
414 | } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUp]) {
415 | // When the buffer is good
416 | if (self.playerItem.playbackLikelyToKeepUp) {
417 | self.loadState = ZFPlayerLoadStatePlayable;
418 | if (self.isPlaying) [self play];
419 | }
420 | } else if ([keyPath isEqualToString:kLoadedTimeRanges]) {
421 | NSTimeInterval bufferTime = [self availableDuration];
422 | self->_bufferTime = bufferTime;
423 | if (self.playerBufferTimeChanged) self.playerBufferTimeChanged(self, bufferTime);
424 | } else if ([keyPath isEqualToString:kPresentationSize]) {
425 | self->_presentationSize = self.playerItem.presentationSize;
426 | if (self.presentationSizeChanged) {
427 | self.presentationSizeChanged(self, self->_presentationSize);
428 | }
429 | } else {
430 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
431 | }
432 | });
433 | }
434 |
435 | #pragma mark - getter
436 |
437 | - (UIView *)view {
438 | if (!_view) {
439 | _view = [[ZFPlayerPresentView alloc] init];
440 | }
441 | return _view;
442 | }
443 |
444 | - (float)rate {
445 | return _rate == 0 ?1:_rate;
446 | }
447 |
448 | - (NSTimeInterval)totalTime {
449 | NSTimeInterval sec = CMTimeGetSeconds(self.player.currentItem.duration);
450 | if (isnan(sec)) {
451 | return 0;
452 | }
453 | return sec;
454 | }
455 |
456 | - (NSTimeInterval)currentTime {
457 | NSTimeInterval sec = CMTimeGetSeconds(self.playerItem.currentTime);
458 | if (isnan(sec) || sec < 0) {
459 | return 0;
460 | }
461 | return sec;
462 | }
463 |
464 | - (NSArray *)playableSuffixArray
465 | {
466 | if (!_playableSuffixArray) {
467 | _playableSuffixArray = @[@"WMV",@"AVI",@"MKV",@"RMVB",@"RM",@"XVID",@"MP4",@"3GP",@"MPG"];
468 | }
469 | return _playableSuffixArray;
470 | }
471 |
472 | #pragma mark - setter
473 |
474 | - (void)setPlayState:(ZFPlayerPlaybackState)playState {
475 | _playState = playState;
476 | if (self.playerPlayStateChanged) self.playerPlayStateChanged(self, playState);
477 | }
478 |
479 | - (void)setLoadState:(ZFPlayerLoadState)loadState {
480 | _loadState = loadState;
481 | if (self.playerLoadStateChanged) self.playerLoadStateChanged(self, loadState);
482 | }
483 |
484 | - (void)setAssetURL:(NSURL *)assetURL {
485 | if (self.player) [self stop];
486 | // 如果有缓存,直接取本地缓存
487 | NSURL *url = [KTVHTTPCache cacheCompleteFileURLWithURL:assetURL];
488 | NSString *suffix = [url pathExtension];
489 | if (url && [self isPlayableSuffix:suffix]) {
490 | _assetURL = url;
491 | }else {
492 | // 设置代理
493 | _assetURL = [KTVHTTPCache proxyURLWithOriginalURL:assetURL];
494 | }
495 | [self prepareToPlay];
496 | }
497 |
498 | - (void)setRate:(float)rate {
499 | _rate = rate;
500 | if (self.player && fabsf(_player.rate) > 0.00001f) {
501 | self.player.rate = rate;
502 | }
503 | }
504 |
505 | - (void)setMuted:(BOOL)muted {
506 | _muted = muted;
507 | self.player.muted = muted;
508 | }
509 |
510 | - (void)setScalingMode:(ZFPlayerScalingMode)scalingMode {
511 | _scalingMode = scalingMode;
512 | ZFPlayerPresentView *presentView = (ZFPlayerPresentView *)self.view;
513 | [CATransaction begin];
514 | [CATransaction setDisableActions:YES];
515 | switch (scalingMode) {
516 | case ZFPlayerScalingModeNone:
517 | presentView.videoGravity = AVLayerVideoGravityResizeAspect;
518 | break;
519 | case ZFPlayerScalingModeAspectFit:
520 | presentView.videoGravity = AVLayerVideoGravityResizeAspect;
521 | break;
522 | case ZFPlayerScalingModeAspectFill:
523 | presentView.videoGravity = AVLayerVideoGravityResizeAspectFill;
524 | break;
525 | case ZFPlayerScalingModeFill:
526 | presentView.videoGravity = AVLayerVideoGravityResize;
527 | break;
528 | default:
529 | break;
530 | }
531 | [CATransaction commit];
532 | }
533 |
534 | - (void)setVolume:(float)volume {
535 | _volume = MIN(MAX(0, volume), 1);
536 | self.player.volume = volume;
537 | }
538 |
539 |
540 | @end
541 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Player/ZFDouYinControlView.h:
--------------------------------------------------------------------------------
1 | //
2 | // ZFDouYinControlView.h
3 | // ZFPlayer_Example
4 | //
5 | // Created by 任子丰 on 2018/6/4.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | @interface ZFDouYinControlView : UIView
13 |
14 | - (void)resetControlView;
15 |
16 | - (void)showCoverViewWithUrl:(NSString *)coverUrl withImageMode:(UIViewContentMode)contentMode;
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Player/ZFDouYinControlView.m:
--------------------------------------------------------------------------------
1 | //
2 | // ZFDouYinControlView.m
3 | // ZFPlayer_Example
4 | //
5 | // Created by 任子丰 on 2018/6/4.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import "ZFDouYinControlView.h"
10 | #import
11 | #import
12 | #import
13 | #import "ZFLoadingView.h"
14 | #import
15 |
16 | @interface ZFDouYinControlView ()
17 | /// 封面图
18 | @property (nonatomic, strong) UIImageView *coverImageView;
19 | @property (nonatomic, strong) UIButton *playBtn;
20 | @property (nonatomic, strong) ZFSliderView *sliderView;
21 | @property (nonatomic, strong) UIImageView *bgImgView;
22 | @property (nonatomic, strong) UIView *effectView;
23 |
24 | @end
25 |
26 | @implementation ZFDouYinControlView
27 | @synthesize player = _player;
28 |
29 | - (instancetype)init {
30 | self = [super init];
31 | if (self) {
32 | [self addSubview:self.playBtn];
33 | [self addSubview:self.sliderView];
34 | [self resetControlView];
35 | }
36 | return self;
37 | }
38 |
39 | - (void)layoutSubviews {
40 | [super layoutSubviews];
41 | self.coverImageView.frame = self.player.currentPlayerManager.view.bounds;
42 |
43 | CGFloat min_x = 0;
44 | CGFloat min_y = 0;
45 | CGFloat min_w = 0;
46 | CGFloat min_h = 0;
47 | CGFloat min_view_w = self.zf_width;
48 | CGFloat min_view_h = self.zf_height;
49 |
50 | min_w = 100;
51 | min_h = 100;
52 | self.playBtn.frame = CGRectMake(min_x, min_y, min_w, min_h);
53 | self.playBtn.center = self.center;
54 |
55 | min_x = 0;
56 | min_y = min_view_h - 80;
57 | min_w = min_view_w;
58 | min_h = 1;
59 | self.sliderView.frame = CGRectMake(min_x, min_y, min_w, min_h);
60 |
61 | self.bgImgView.frame = self.bounds;
62 | self.effectView.frame = self.bgImgView.bounds;
63 | }
64 |
65 | - (void)resetControlView {
66 | self.playBtn.hidden = YES;
67 | self.sliderView.value = 0;
68 | self.sliderView.bufferValue = 0;
69 | self.coverImageView.contentMode = UIViewContentModeScaleAspectFit;
70 | }
71 |
72 | /// 加载状态改变
73 | - (void)videoPlayer:(ZFPlayerController *)videoPlayer loadStateChanged:(ZFPlayerLoadState)state {
74 | if (state == ZFPlayerLoadStatePrepare) {
75 | self.coverImageView.hidden = NO;
76 | } else if (state == ZFPlayerLoadStatePlaythroughOK || state == ZFPlayerLoadStatePlayable) {
77 | self.coverImageView.hidden = YES;
78 | self.effectView.hidden = NO;
79 | }
80 | if ((state == ZFPlayerLoadStateStalled || state == ZFPlayerLoadStatePrepare) && videoPlayer.currentPlayerManager.isPlaying) {
81 | [self.sliderView startAnimating];
82 | } else {
83 | [self.sliderView stopAnimating];
84 | }
85 | }
86 |
87 | /// 播放进度改变回调
88 | - (void)videoPlayer:(ZFPlayerController *)videoPlayer currentTime:(NSTimeInterval)currentTime totalTime:(NSTimeInterval)totalTime {
89 | self.sliderView.value = videoPlayer.progress;
90 | }
91 |
92 | - (void)gestureSingleTapped:(ZFPlayerGestureControl *)gestureControl {
93 | if (self.player.currentPlayerManager.isPlaying) {
94 | [self.player.currentPlayerManager pause];
95 | self.playBtn.hidden = NO;
96 | self.playBtn.transform = CGAffineTransformMakeScale(1.5f, 1.5f);
97 | [UIView animateWithDuration:0.2f delay:0
98 | options:UIViewAnimationOptionCurveEaseIn animations:^{
99 | self.playBtn.transform = CGAffineTransformIdentity;
100 | } completion:^(BOOL finished) {
101 | }];
102 | } else {
103 | [self.player.currentPlayerManager play];
104 | self.playBtn.hidden = YES;
105 | }
106 | }
107 |
108 | - (void)setPlayer:(ZFPlayerController *)player {
109 | _player = player;
110 | [player.currentPlayerManager.view insertSubview:self.bgImgView atIndex:0];
111 | [self.bgImgView addSubview:self.effectView];
112 | [player.currentPlayerManager.view insertSubview:self.coverImageView atIndex:1];
113 | }
114 |
115 | - (void)showCoverViewWithUrl:(NSString *)coverUrl withImageMode:(UIViewContentMode)contentMode {
116 | self.coverImageView.contentMode = contentMode;
117 | [self.coverImageView setImageWithURLString:coverUrl placeholder:[UIImage imageNamed:@"img_video_loading"]];
118 | [self.bgImgView setImageWithURLString:coverUrl placeholder:[UIImage imageNamed:@"img_video_loading"]];
119 | }
120 |
121 | - (BOOL)gestureTriggerCondition:(ZFPlayerGestureControl *)gestureControl
122 | gestureType:(ZFPlayerGestureType)gestureType
123 | gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
124 | touch:(UITouch *)touch
125 | {
126 | if (gestureType == ZFPlayerGestureTypePan) {
127 | return NO;
128 | }
129 | return YES;
130 | }
131 |
132 | #pragma mark - getter
133 |
134 | - (UIImageView *)bgImgView {
135 | if (!_bgImgView) {
136 | _bgImgView = [[UIImageView alloc] init];
137 | _bgImgView.userInteractionEnabled = YES;
138 | }
139 | return _bgImgView;
140 | }
141 |
142 | - (UIView *)effectView {
143 | if (!_effectView) {
144 | if (@available(iOS 8.0, *)) {
145 | UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
146 | _effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
147 | } else {
148 | UIToolbar *effectView = [[UIToolbar alloc] init];
149 | effectView.barStyle = UIBarStyleBlackTranslucent;
150 | _effectView = effectView;
151 | }
152 | }
153 | return _effectView;
154 | }
155 |
156 | - (UIButton *)playBtn {
157 | if (!_playBtn) {
158 | _playBtn = [UIButton buttonWithType:UIButtonTypeCustom];
159 | _playBtn.userInteractionEnabled = NO;
160 | [_playBtn setImage:[UIImage imageNamed:@"icon_play_pause"] forState:UIControlStateNormal];
161 | }
162 | return _playBtn;
163 | }
164 |
165 | - (ZFSliderView *)sliderView {
166 | if (!_sliderView) {
167 | _sliderView = [[ZFSliderView alloc] init];
168 | _sliderView.maximumTrackTintColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.2];
169 | _sliderView.minimumTrackTintColor = [UIColor whiteColor];
170 | _sliderView.bufferTrackTintColor = [UIColor clearColor];
171 | _sliderView.sliderHeight = 1;
172 | _sliderView.isHideSliderBlock = NO;
173 | }
174 | return _sliderView;
175 | }
176 |
177 | - (UIImageView *)coverImageView {
178 | if (!_coverImageView) {
179 | _coverImageView = [[UIImageView alloc] init];
180 | _coverImageView.userInteractionEnabled = YES;
181 | _coverImageView.clipsToBounds = YES;
182 | }
183 | return _coverImageView;
184 | }
185 |
186 | @end
187 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Transition/MPTransition.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPTransition.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/31.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @class MPPlayerController;
14 |
15 | /// 用于视频信息流的转场动画
16 | @interface MPTransition : NSObject
17 |
18 | /**
19 | 初始化动画
20 |
21 | @param duration 动画时长
22 | @param startView 开始视图
23 | @param startImage 开始图片
24 | @param player 播放器
25 | @param operation 动画形式
26 | @param completion 动画完成block
27 | @return 动画实例
28 | */
29 | + (instancetype)animationWithDuration:(NSTimeInterval)duration
30 | startView:(UIView *)startView
31 | startImage:(UIImage *)startImage
32 | player: (MPPlayerController *)player
33 | operation:(UINavigationControllerOperation)operation
34 | completion:(void(^)(void))completion;
35 |
36 | @end
37 |
38 | NS_ASSUME_NONNULL_END
39 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Transition/MPTransition.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPTransition.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/31.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPTransition.h"
10 | #import "MPPlayerController.h"
11 | #import
12 |
13 | @interface MPTransition()
14 |
15 | @property (nonatomic, strong) UIView *startView;
16 | @property (nonatomic, strong) UIImage *startImage;
17 | @property (nonatomic, assign) NSTimeInterval duration;
18 | @property (nonatomic, strong) MPPlayerController *player;
19 | @property (nonatomic, assign) UINavigationControllerOperation operation;
20 | @property (nonatomic, assign) void(^completion)(void);
21 | @property (nonatomic, strong) UIView *effectView;
22 |
23 | @end
24 |
25 | @implementation MPTransition
26 |
27 | + (instancetype)animationWithDuration:(NSTimeInterval)duration
28 | startView:(UIView *)startView
29 | startImage:(UIImage *)startImage
30 | player: (MPPlayerController *)player
31 | operation:(UINavigationControllerOperation)operation
32 | completion:(void(^)(void))completion
33 | {
34 | MPTransition *animation = [MPTransition new];
35 | animation.player = player;
36 | animation.duration = duration;
37 | animation.startView = startView;
38 | animation.startImage = startImage;
39 | animation.operation = operation;
40 | animation.completion = completion;
41 |
42 | return animation;
43 | }
44 |
45 | - (void)animateTransition:(id)transitionContext {
46 | if (self.operation == UINavigationControllerOperationPush) {
47 | [self startPushAnimation: transitionContext];
48 | }else {
49 | [self startPopAnimation: transitionContext];
50 | }
51 | }
52 |
53 | - (void)startPushAnimation:(id)transitionContext
54 | {
55 | // 获取 fromView 和 toView
56 | UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
57 | UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
58 |
59 | // 添加到动画容器视图中
60 | [[transitionContext containerView] addSubview:fromView];
61 | [[transitionContext containerView] addSubview:toView];
62 |
63 | UIImageView *bgImgView = [[UIImageView alloc] initWithFrame:fromView.bounds];
64 | bgImgView.contentMode = UIViewContentModeScaleAspectFill;
65 | bgImgView.image = self.startImage;
66 | UIView *colorCover = [[UIView alloc] init];
67 | colorCover.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4];
68 | colorCover.frame = fromView.bounds;
69 | [bgImgView addSubview:colorCover];
70 | [bgImgView addSubview:self.effectView];
71 | [[transitionContext containerView] addSubview:bgImgView];
72 |
73 | // 创建player容器
74 | CGRect winFrame = CGRectZero;
75 | if (self.startView) {
76 | winFrame = [self.startView convertRect:self.startView.bounds toView:nil];
77 | }
78 |
79 | UIImageView *playerContainer = [[UIImageView alloc] initWithFrame:winFrame];
80 | playerContainer.image = self.startImage;
81 | playerContainer.contentMode = UIViewContentModeScaleAspectFit;
82 | [[transitionContext containerView] addSubview:playerContainer];
83 | if (self.player) {
84 | self.player.currentPlayerManager.scalingMode = self.player.videoFlowScalingMode;
85 | self.player.currentPlayerManager.view.backgroundColor = [UIColor clearColor];
86 | [self.player updateNoramlPlayerWithContainerView:playerContainer];
87 | }
88 | CGFloat bottomOffset = iPhoneX ? 83 : 0;
89 | NSTimeInterval duration = [self transitionDuration:transitionContext];
90 | CGRect targetFrame = CGRectMake(0, 0, ZFPlayer_ScreenWidth, ZFPlayer_ScreenHeight - bottomOffset);
91 |
92 | toView.alpha = 0.0f;
93 | bgImgView.alpha = 0;
94 | [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
95 | // mask 渐变效果
96 | bgImgView.alpha = 1;
97 | playerContainer.frame = targetFrame;
98 | } completion:^(BOOL finished) {
99 | toView.alpha = 1.0f;
100 | // 移除临时视图
101 | [bgImgView removeFromSuperview];
102 | [playerContainer removeFromSuperview];
103 | // 结束动画
104 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
105 | if (self.completion) {
106 | self.completion();
107 | }
108 | }];
109 | }
110 |
111 | - (void)startPopAnimation: (id)transitionContext
112 | {
113 | // 获取 fromView 和 toView
114 | UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
115 | UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
116 | self.player.currentPlayerManager.view.backgroundColor = [UIColor blackColor];
117 |
118 | // 添加到动画容器视图中
119 | UIView *container = [transitionContext containerView];
120 | [container addSubview:toView];
121 | [container addSubview:fromView];
122 | container.backgroundColor = [UIColor clearColor];
123 |
124 | // 添加动画临时视图到 fromView
125 | CGFloat bottomOffset = iPhoneX ? 83 : 0;
126 | CGRect normalFrame = CGRectMake(0, 0, ZFPlayer_ScreenWidth, ZFPlayer_ScreenHeight - bottomOffset);
127 | CGRect winFrame = CGRectZero;
128 | if (self.startView) {
129 | winFrame = [self.startView convertRect:self.startView.bounds toView:nil];
130 | }
131 |
132 | // 显示图片
133 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:normalFrame];
134 | imageView.contentMode = UIViewContentModeScaleAspectFit;
135 | imageView.clipsToBounds = YES;
136 | imageView.image = self.startImage;
137 | if (self.player) {
138 | // pop回去的时候,设置回原来的scalingMode
139 | self.player.currentPlayerManager.scalingMode = ZFPlayerScalingModeAspectFill;
140 | [self.player updateNoramlPlayerWithContainerView:imageView];
141 | }
142 |
143 | [container addSubview:imageView];
144 |
145 | toView.alpha = 1;
146 | fromView.alpha = 1;
147 | NSTimeInterval duration = [self transitionDuration:transitionContext];
148 |
149 | [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
150 | imageView.frame = winFrame;
151 | fromView.alpha = 0;
152 | } completion:^(BOOL finished) {
153 | // 移除临时视图
154 | [imageView removeFromSuperview];
155 |
156 | // 结束动画
157 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
158 |
159 | if (self.completion) {
160 | self.completion();
161 | }
162 | }];
163 | }
164 |
165 | - (NSTimeInterval)transitionDuration:(id)transitionContext {
166 | return self.duration;
167 | }
168 |
169 | - (UIView *)effectView {
170 | if (!_effectView) {
171 | if (@available(iOS 8.0, *)) {
172 | UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
173 | _effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
174 | } else {
175 | UIToolbar *effectView = [[UIToolbar alloc] init];
176 | effectView.barStyle = UIBarStyleBlackTranslucent;
177 | _effectView = effectView;
178 | }
179 | }
180 | return _effectView;
181 | }
182 |
183 | @end
184 |
185 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Transition/MPUserDynamicTransition.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPUserDynamicTransition.h
3 | //
4 | // Created by Maple on 2019/12/5.
5 | //
6 |
7 | #import
8 |
9 | NS_ASSUME_NONNULL_BEGIN
10 |
11 | /// 关注的用户动态转场
12 | @interface MPUserDynamicTransition : NSObject
13 | /// 是否手势退出
14 | @property (nonatomic, assign) BOOL isInteracting;
15 | /// 是否手势完成
16 | @property (nonatomic, assign) BOOL isComplete;
17 | /**
18 | 初始化动画
19 |
20 | @param duration 动画时长
21 | @param startView 开始视图
22 | @param startImage 开始图片
23 | @param endX pop动画时的endX
24 | @param operation 动画形式
25 | @return 动画实例
26 | */
27 | + (instancetype)animationWithDuration:(NSTimeInterval)duration
28 | startView:(UIView *)startView
29 | startImage:(UIImage *)startImage
30 | endX: (CGFloat)endX
31 | operation:(UINavigationControllerOperation)operation;
32 |
33 |
34 | @end
35 |
36 | NS_ASSUME_NONNULL_END
37 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/Transition/MPUserDynamicTransition.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPUserDynamicTransition.m
3 | //
4 | // Created by Maple on 2019/12/5.
5 | //
6 |
7 | #import "MPUserDynamicTransition.h"
8 | #import
9 |
10 | @interface MPUserDynamicTransition ()
11 |
12 | @property (nonatomic, assign) NSTimeInterval duration;
13 | @property (nonatomic, strong) UIView *startView;
14 | @property (nonatomic, strong) UIImage *startImage;
15 | @property (nonatomic, strong) UICollectionView *collectionView;
16 | @property (nonatomic, assign) NSInteger index;
17 | @property (nonatomic, assign) CGFloat endX;
18 | @property (nonatomic, assign) UINavigationControllerOperation operation;
19 |
20 | @end
21 |
22 | @implementation MPUserDynamicTransition
23 |
24 | + (instancetype)animationWithDuration:(NSTimeInterval)duration
25 | startView:(UIView *)startView
26 | startImage:(UIImage *)startImage
27 | endX: (CGFloat)endX
28 | operation:(UINavigationControllerOperation)operation
29 | {
30 | MPUserDynamicTransition *transition = [[self alloc] init];
31 | transition.duration = duration;
32 | transition.startView = startView;
33 | transition.startImage = startImage;
34 | transition.endX = endX;
35 | transition.operation = operation;
36 | return transition;
37 | }
38 |
39 | - (NSTimeInterval)transitionDuration:(nullable id)transitionContext {
40 | return self.duration;
41 | }
42 |
43 | - (void)animateTransition:(nonnull id)transitionContext {
44 | if (self.operation == UINavigationControllerOperationPush) {
45 | [self startPushAnimation: transitionContext];
46 | }else {
47 | [self startPopAnimation: transitionContext];
48 | }
49 | }
50 |
51 | - (void)startPushAnimation: (nonnull id)transitionContext
52 | {
53 | UIView *contentView = [transitionContext containerView];
54 | // 获取 fromView 和 toView
55 | UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
56 | UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
57 | [contentView addSubview:fromView];
58 | [contentView addSubview:toView];
59 |
60 | UIImageView *imageView = [[UIImageView alloc] init];
61 | imageView.contentMode = UIViewContentModeScaleAspectFill;
62 | imageView.clipsToBounds = YES;
63 | imageView.layer.cornerRadius = self.startView.bounds.size.width * 0.5;
64 | imageView.layer.masksToBounds = YES;
65 | imageView.image = self.startImage;
66 | CGRect winFrame = CGRectZero;
67 | if (self.startView) {
68 | winFrame = [self.startView convertRect:self.startView.bounds toView:nil];
69 | }
70 | imageView.frame = winFrame;
71 | toView.center = imageView.center;
72 | CGFloat scale = 56.0 / ZFPlayer_ScreenWidth;
73 | toView.transform = CGAffineTransformMakeScale(scale, scale);
74 | [UIView animateWithDuration:self.duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
75 | toView.transform = CGAffineTransformIdentity;
76 | toView.center = CGPointMake(ZFPlayer_ScreenWidth * 0.5, ZFPlayer_ScreenHeight * 0.5);
77 | } completion:^(BOOL finished) {
78 | // 结束动画
79 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
80 | }];
81 | }
82 |
83 | - (void)startPopAnimation: (nonnull id)transitionContext
84 | {
85 | UIView *contentView = [transitionContext containerView];
86 | // 获取 fromView 和 toView
87 | UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
88 | UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
89 | UIView *whiteCoverView = [[UIView alloc] init];
90 | whiteCoverView.backgroundColor = [UIColor blackColor];
91 | whiteCoverView.frame = CGRectMake(0, 0, ZFPlayer_ScreenWidth, ZFPlayer_ScreenHeight);
92 | whiteCoverView.alpha = 0;
93 | [contentView addSubview:toView];
94 | [contentView addSubview:whiteCoverView];
95 | [contentView addSubview:fromView];
96 |
97 | UIImageView *imageView = [[UIImageView alloc] init];
98 | imageView.contentMode = UIViewContentModeScaleAspectFill;
99 | imageView.clipsToBounds = YES;
100 | imageView.image = self.startImage;
101 | imageView.layer.cornerRadius = ZFPlayer_ScreenWidth * 0.5;
102 | CGFloat top = (ZFPlayer_ScreenHeight - ZFPlayer_ScreenWidth) * 0.5;
103 | CGRect winFrame = CGRectMake(0, top, ZFPlayer_ScreenWidth, ZFPlayer_ScreenWidth);
104 | imageView.frame = winFrame;
105 | imageView.hidden = YES;
106 | [contentView addSubview:imageView];
107 |
108 | CGFloat targetCorner = 0;
109 | CGRect targetFrame = CGRectZero;
110 | if (self.startView) {
111 | targetFrame = [self.startView convertRect:self.startView.bounds toView:nil];
112 | targetFrame = CGRectMake(self.endX, targetFrame.origin.y, targetFrame.size.width, targetFrame.size.height);
113 | targetCorner = self.startView.bounds.size.width * 0.5;
114 | }
115 | dispatch_block_t block = dispatch_block_create(0, ^{
116 | imageView.hidden = NO;
117 | toView.alpha = 1.0f;
118 | fromView.transform = CGAffineTransformIdentity;
119 | fromView.alpha = 0.0f;
120 | whiteCoverView.alpha = 0.4;
121 | [UIView animateWithDuration:self.duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
122 | whiteCoverView.alpha = 0;
123 | imageView.frame = targetFrame;
124 | imageView.layer.cornerRadius = targetCorner;
125 | } completion:^(BOOL finished) {
126 | [imageView removeFromSuperview];
127 | [whiteCoverView removeFromSuperview];
128 | // 结束动画
129 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
130 | }];
131 | });
132 | if (self.isInteracting) {
133 | whiteCoverView.alpha = 1;
134 | [UIView animateWithDuration:self.duration animations:^{
135 | whiteCoverView.alpha = 0.4;
136 | fromView.transform = CGAffineTransformScale(fromView.transform, 0.9, 0.9);
137 | fromView.transform = CGAffineTransformTranslate(fromView.transform, 0, ZFPlayer_ScreenHeight * 0.5);
138 | } completion:^(BOOL finished) {
139 | if (self.isComplete) {
140 | block();
141 | }else {
142 | [imageView removeFromSuperview];
143 | [whiteCoverView removeFromSuperview];
144 | // 结束动画
145 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
146 | }
147 | }];
148 | }else {
149 | block();
150 | }
151 | }
152 |
153 | - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
154 | // 标记转场结束
155 | id transitionContext = [anim valueForKey:@"transitionContext"];
156 | // 结束动画
157 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
158 | }
159 |
160 | @end
161 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/View/MPCardLayoutCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPCardLayoutCell.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MPCardLayoutCell : UICollectionViewCell
14 |
15 | @property (nonatomic, strong) UIImageView *coverImageView;
16 |
17 | @end
18 |
19 | NS_ASSUME_NONNULL_END
20 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/View/MPCardLayoutCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPCardLayoutCell.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPCardLayoutCell.h"
10 |
11 | @implementation MPCardLayoutCell
12 |
13 | - (instancetype)initWithFrame:(CGRect)frame
14 | {
15 | self = [super initWithFrame:frame];
16 | self.coverImageView = [[UIImageView alloc] init];
17 | self.coverImageView.contentMode = UIViewContentModeScaleAspectFill;
18 | self.coverImageView.layer.masksToBounds = YES;
19 | [self.contentView addSubview:self.coverImageView];
20 | return self;
21 | }
22 |
23 | - (void)layoutSubviews
24 | {
25 | [super layoutSubviews];
26 | self.coverImageView.frame = self.bounds;
27 | }
28 |
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/View/MPUserDynamicCollectionViewCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPUserDynamicCollectionViewCell.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/18.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface MPUserDynamicCollectionViewCell : UICollectionViewCell
14 |
15 | @property (nonatomic, strong) UIImageView *iconImageView;
16 |
17 | @end
18 |
19 | NS_ASSUME_NONNULL_END
20 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/View/MPUserDynamicCollectionViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPUserDynamicCollectionViewCell.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/3/18.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPUserDynamicCollectionViewCell.h"
10 |
11 | @implementation MPUserDynamicCollectionViewCell
12 |
13 | - (instancetype)initWithFrame:(CGRect)frame
14 | {
15 | self = [super initWithFrame:frame];
16 | [self setup];
17 | return self;
18 | }
19 |
20 | - (void)setup
21 | {
22 | self.iconImageView = [[UIImageView alloc] init];
23 | self.iconImageView.contentMode = UIViewContentModeScaleAspectFill;
24 | self.iconImageView.layer.cornerRadius = 40;
25 | self.iconImageView.layer.masksToBounds = YES;
26 | self.iconImageView.layer.borderWidth = 1;
27 | self.iconImageView.layer.borderColor = [UIColor orangeColor].CGColor;
28 | self.iconImageView.frame = CGRectMake(0, 0, 80, 80);
29 | [self.contentView addSubview:self.iconImageView];
30 | }
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/View/MPWaterFallCollectionViewCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPWaterFallCollectionViewCell.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/1/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "ZFTableData.h"
11 |
12 | @interface MPWaterFallCollectionViewCell : UICollectionViewCell
13 |
14 | @property (nonatomic, strong) UIImageView *imageView;
15 | @property (nonatomic, strong) UILabel *titleLabel;
16 | @property (nonatomic, strong) ZFTableData *data;
17 |
18 | @end
19 |
20 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/View/MPWaterFallCollectionViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPWaterFallCollectionViewCell.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/1/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPWaterFallCollectionViewCell.h"
10 | #import
11 |
12 | @implementation MPWaterFallCollectionViewCell
13 |
14 | - (instancetype)initWithFrame:(CGRect)frame
15 | {
16 | self = [super initWithFrame:frame];
17 | [self setup];
18 | return self;
19 | }
20 |
21 | - (void)setup
22 | {
23 | self.imageView = [[UIImageView alloc] init];
24 | [self.contentView addSubview:self.imageView];
25 | }
26 |
27 | - (void)layoutSubviews
28 | {
29 | [super layoutSubviews];
30 | self.imageView.frame = self.contentView.bounds;
31 | }
32 |
33 | - (void)setData:(ZFTableData *)data
34 | {
35 | _data = data;
36 | [self.imageView setImageWithURLString:data.thumbnail_url placeholder:nil];
37 | }
38 |
39 | @end
40 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/View/ZFDouYinCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // ZFDouYinCell.h
3 | // ZFPlayer_Example
4 | //
5 | // Created by 紫枫 on 2018/6/4.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "ZFTableData.h"
11 |
12 | @interface ZFDouYinCell : UITableViewCell
13 |
14 | @property (nonatomic, strong) ZFTableData *data;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/View/ZFDouYinCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // ZFDouYinCell.m
3 | // ZFPlayer_Example
4 | //
5 | // Created by 紫枫 on 2018/6/4.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import "ZFDouYinCell.h"
10 | #import
11 | #import
12 | #import
13 | #import
14 | #import "ZFLoadingView.h"
15 |
16 | @interface ZFDouYinCell ()
17 |
18 | @property (nonatomic, strong) UIImageView *coverImageView;
19 |
20 | @property (nonatomic, strong) UIButton *likeBtn;
21 |
22 | @property (nonatomic, strong) UIButton *commentBtn;
23 |
24 | @property (nonatomic, strong) UIButton *shareBtn;
25 |
26 | @property (nonatomic, strong) UILabel *titleLabel;
27 |
28 | @property (nonatomic, strong) UIImage *placeholderImage;
29 |
30 | @property (nonatomic, strong) UIImageView *bgImgView;
31 |
32 | @property (nonatomic, strong) UIView *effectView;
33 |
34 | @end
35 |
36 | @implementation ZFDouYinCell
37 |
38 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
39 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
40 | if (self) {
41 | self.selectionStyle = UITableViewCellSelectionStyleNone;
42 | [self.contentView addSubview:self.bgImgView];
43 | [self.bgImgView addSubview:self.effectView];
44 | [self.contentView addSubview:self.coverImageView];
45 | [self.contentView addSubview:self.titleLabel];
46 | [self.contentView addSubview:self.likeBtn];
47 | [self.contentView addSubview:self.commentBtn];
48 | [self.contentView addSubview:self.shareBtn];
49 | }
50 | return self;
51 | }
52 |
53 | - (void)layoutSubviews {
54 | [super layoutSubviews];
55 | self.coverImageView.frame = self.contentView.bounds;
56 | self.bgImgView.frame = self.contentView.bounds;
57 | self.effectView.frame = self.bgImgView.bounds;
58 |
59 | CGFloat min_x = 0;
60 | CGFloat min_y = 0;
61 | CGFloat min_w = 0;
62 | CGFloat min_h = 0;
63 | CGFloat min_view_w = self.zf_width;
64 | CGFloat min_view_h = self.zf_height;
65 | CGFloat margin = 30;
66 |
67 | min_w = 40;
68 | min_h = min_w;
69 | min_x = min_view_w - min_w - 20;
70 | min_y = min_view_h - min_h - 80;
71 | self.shareBtn.frame = CGRectMake(min_x, min_y, min_w, min_h);
72 |
73 | min_w = CGRectGetWidth(self.shareBtn.frame);
74 | min_h = min_w;
75 | min_x = CGRectGetMinX(self.shareBtn.frame);
76 | min_y = CGRectGetMinY(self.shareBtn.frame) - min_h - margin;
77 | self.commentBtn.frame = CGRectMake(min_x, min_y, min_w, min_h);
78 |
79 | min_w = CGRectGetWidth(self.shareBtn.frame);
80 | min_h = min_w;
81 | min_x = CGRectGetMinX(self.commentBtn.frame);
82 | min_y = CGRectGetMinY(self.commentBtn.frame) - min_h - margin;
83 | self.likeBtn.frame = CGRectMake(min_x, min_y, min_w, min_h);
84 |
85 | min_x = 20;
86 | min_h = 20;
87 | min_y = min_view_h - min_h - 50;
88 | min_w = self.likeBtn.zf_left - margin;
89 | self.titleLabel.frame = CGRectMake(min_x, min_y, min_w, min_h);
90 | }
91 |
92 | - (UILabel *)titleLabel {
93 | if (!_titleLabel) {
94 | _titleLabel = [UILabel new];
95 | _titleLabel.textColor = [UIColor whiteColor];
96 | _titleLabel.font = [UIFont systemFontOfSize:15];
97 | }
98 | return _titleLabel;
99 | }
100 |
101 | - (UIButton *)likeBtn {
102 | if (!_likeBtn) {
103 | _likeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
104 | [_likeBtn setImage:[UIImage imageNamed:@"like"] forState:UIControlStateNormal];
105 | }
106 | return _likeBtn;
107 | }
108 |
109 |
110 | - (UIButton *)commentBtn {
111 | if (!_commentBtn) {
112 | _commentBtn = [UIButton buttonWithType:UIButtonTypeCustom];
113 | [_commentBtn setImage:[UIImage imageNamed:@"comment"] forState:UIControlStateNormal];
114 | }
115 | return _commentBtn;
116 | }
117 |
118 | - (UIButton *)shareBtn {
119 | if (!_shareBtn) {
120 | _shareBtn = [UIButton buttonWithType:UIButtonTypeCustom];
121 | [_shareBtn setImage:[UIImage imageNamed:@"share"] forState:UIControlStateNormal];
122 | }
123 | return _shareBtn;
124 | }
125 |
126 | - (UIImage *)placeholderImage {
127 | if (!_placeholderImage) {
128 | _placeholderImage = [ZFUtilities imageWithColor:[UIColor colorWithRed:220/255.0 green:220/255.0 blue:220/255.0 alpha:1] size:CGSizeMake(1, 1)];
129 | }
130 | return _placeholderImage;
131 | }
132 |
133 | - (void)setData:(ZFTableData *)data {
134 | _data = data;
135 | if (data.thumbnail_width >= data.thumbnail_height) {
136 | self.coverImageView.contentMode = UIViewContentModeScaleAspectFit;
137 | self.coverImageView.clipsToBounds = NO;
138 | } else {
139 | self.coverImageView.contentMode = UIViewContentModeScaleAspectFill;
140 | self.coverImageView.clipsToBounds = YES;
141 | }
142 | [self.coverImageView setImageWithURLString:data.thumbnail_url placeholder:[UIImage imageNamed:@"loading_bgView"]];
143 | [self.bgImgView setImageWithURLString:data.thumbnail_url placeholder:[UIImage imageNamed:@"loading_bgView"]];
144 | self.titleLabel.text = data.title;
145 | }
146 |
147 | - (UIImageView *)coverImageView {
148 | if (!_coverImageView) {
149 | _coverImageView = [[UIImageView alloc] init];
150 | _coverImageView.userInteractionEnabled = YES;
151 | _coverImageView.tag = 10086;
152 | // _coverImageView.contentMode = UIViewContentModeScaleAspectFit;
153 | }
154 | return _coverImageView;
155 | }
156 |
157 | - (UIImageView *)bgImgView {
158 | if (!_bgImgView) {
159 | _bgImgView = [[UIImageView alloc] init];
160 | _bgImgView.userInteractionEnabled = YES;
161 | }
162 | return _bgImgView;
163 | }
164 |
165 | - (UIView *)effectView {
166 | if (!_effectView) {
167 | if (@available(iOS 8.0, *)) {
168 | UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
169 | _effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
170 | } else {
171 | UIToolbar *effectView = [[UIToolbar alloc] init];
172 | effectView.barStyle = UIBarStyleBlackTranslucent;
173 | _effectView = effectView;
174 | }
175 | }
176 | return _effectView;
177 | }
178 |
179 | @end
180 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/View/ZFTableViewCell.h:
--------------------------------------------------------------------------------
1 | //
2 | // ZFTableViewCell.h
3 | // ZFPlayer
4 | //
5 | // Created by 紫枫 on 2018/4/3.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "ZFTableData.h"
11 | #import "ZFTableViewCellLayout.h"
12 |
13 | @protocol ZFTableViewCellDelegate
14 |
15 | - (void)zf_playTheVideoAtIndexPath:(NSIndexPath *)indexPath;
16 |
17 | @end
18 |
19 | @interface ZFTableViewCell : UITableViewCell
20 |
21 | @property (nonatomic, strong) ZFTableViewCellLayout *layout;
22 | @property (nonatomic, strong, readonly) UIImageView *coverImageView;
23 | @property (nonatomic, copy) void(^playCallback)(void);
24 |
25 | - (void)setDelegate:(id)delegate withIndexPath:(NSIndexPath *)indexPath;
26 |
27 | - (void)showMaskView;
28 |
29 | - (void)hideMaskView;
30 |
31 | - (void)setNormalMode;
32 |
33 | @end
34 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/View/ZFTableViewCell.m:
--------------------------------------------------------------------------------
1 | //
2 | // ZFTableViewCell.m
3 | // ZFPlayer
4 | //
5 | // Created by 紫枫 on 2018/4/3.
6 | // Copyright © 2018年 紫枫. All rights reserved.
7 | //
8 |
9 | #import "ZFTableViewCell.h"
10 | #import
11 |
12 | @interface ZFTableViewCell ()
13 | @property (nonatomic, strong) UIImageView *headImageView;
14 | @property (nonatomic, strong) UILabel *nickNameLabel;
15 | @property (nonatomic, strong) UIImageView *coverImageView;
16 | @property (nonatomic, strong) UIView *fullMaskView;
17 | @property (nonatomic, strong) UIButton *playBtn;
18 | @property (nonatomic, strong) UILabel *titleLabel;
19 | @property (nonatomic, weak) id delegate;
20 | @property (nonatomic, strong) NSIndexPath *indexPath;
21 |
22 | @property (nonatomic, strong) UIImageView *bgImgView;
23 |
24 | @property (nonatomic, strong) UIView *effectView;
25 |
26 | @end
27 |
28 | @implementation ZFTableViewCell
29 |
30 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
31 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
32 | if (self) {
33 | [self.contentView addSubview:self.headImageView];
34 | [self.contentView addSubview:self.nickNameLabel];
35 | [self.contentView addSubview:self.bgImgView];
36 | [self.bgImgView addSubview:self.effectView];
37 | [self.contentView addSubview:self.coverImageView];
38 | [self.coverImageView addSubview:self.playBtn];
39 | [self.contentView addSubview:self.titleLabel];
40 | [self.contentView addSubview:self.fullMaskView];
41 | self.contentView.backgroundColor = [UIColor blackColor];
42 | self.selectionStyle = UITableViewCellSelectionStyleNone;
43 | }
44 | return self;
45 | }
46 |
47 | - (void)setLayout:(ZFTableViewCellLayout *)layout {
48 | _layout = layout;
49 | self.headImageView.frame = layout.headerRect;
50 | self.nickNameLabel.frame = layout.nickNameRect;
51 | self.coverImageView.frame = layout.videoRect;
52 | self.bgImgView.frame = layout.videoRect;
53 | self.effectView.frame = self.bgImgView.bounds;
54 | self.titleLabel.frame = layout.titleLabelRect;
55 | self.playBtn.frame = layout.playBtnRect;
56 | self.fullMaskView.frame = layout.maskViewRect;
57 |
58 | [self.headImageView setImageWithURLString:layout.data.head placeholder:[UIImage imageNamed:@"defaultUserIcon"]];
59 | [self.coverImageView setImageWithURLString:layout.data.thumbnail_url placeholder:[UIImage imageNamed:@"loading_bgView"]];
60 | [self.bgImgView setImageWithURLString:layout.data.thumbnail_url placeholder:[UIImage imageNamed:@"loading_bgView"]];
61 | self.nickNameLabel.text = layout.data.nick_name;
62 | self.titleLabel.text = layout.data.title;
63 | }
64 |
65 | - (void)setDelegate:(id)delegate withIndexPath:(NSIndexPath *)indexPath {
66 | self.delegate = delegate;
67 | self.indexPath = indexPath;
68 | }
69 |
70 | - (void)setNormalMode {
71 | self.fullMaskView.hidden = YES;
72 | self.titleLabel.textColor = [UIColor blackColor];
73 | self.nickNameLabel.textColor = [UIColor blackColor];
74 | self.contentView.backgroundColor = [UIColor whiteColor];
75 | }
76 |
77 | - (void)showMaskView {
78 | [UIView animateWithDuration:0.3 animations:^{
79 | self.fullMaskView.alpha = 1;
80 | }];
81 | }
82 |
83 | - (void)hideMaskView {
84 | [UIView animateWithDuration:0.3 animations:^{
85 | self.fullMaskView.alpha = 0;
86 | }];
87 | }
88 |
89 |
90 | - (void)playBtnClick:(UIButton *)sender {
91 | if ([self.delegate respondsToSelector:@selector(zf_playTheVideoAtIndexPath:)]) {
92 | [self.delegate zf_playTheVideoAtIndexPath:self.indexPath];
93 | }
94 | }
95 |
96 | #pragma mark - getter
97 |
98 | - (UIButton *)playBtn {
99 | if (!_playBtn) {
100 | _playBtn = [UIButton buttonWithType:UIButtonTypeCustom];
101 | [_playBtn setImage:[UIImage imageNamed:@"new_allPlay_44x44_"] forState:UIControlStateNormal];
102 | [_playBtn addTarget:self action:@selector(playBtnClick:) forControlEvents:UIControlEventTouchUpInside];
103 | }
104 | return _playBtn;
105 | }
106 |
107 | - (UIView *)fullMaskView {
108 | if (!_fullMaskView) {
109 | _fullMaskView = [UIView new];
110 | _fullMaskView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.8];
111 | _fullMaskView.userInteractionEnabled = NO;
112 | }
113 | return _fullMaskView;
114 | }
115 |
116 | - (UILabel *)titleLabel {
117 | if (!_titleLabel) {
118 | _titleLabel = [UILabel new];
119 | _titleLabel.textColor = [UIColor whiteColor];
120 | _titleLabel.numberOfLines = 0;
121 | _titleLabel.font = [UIFont systemFontOfSize:15];
122 | }
123 | return _titleLabel;
124 | }
125 |
126 | - (UILabel *)nickNameLabel {
127 | if (!_nickNameLabel) {
128 | _nickNameLabel = [UILabel new];
129 | _nickNameLabel.textColor = [UIColor whiteColor];
130 | _nickNameLabel.font = [UIFont systemFontOfSize:15];
131 | }
132 | return _nickNameLabel;
133 | }
134 |
135 | - (UIImageView *)headImageView {
136 | if (!_headImageView) {
137 | _headImageView = [[UIImageView alloc] init];
138 | _headImageView.userInteractionEnabled = YES;
139 | }
140 | return _headImageView;
141 | }
142 |
143 | - (UIImageView *)coverImageView {
144 | if (!_coverImageView) {
145 | _coverImageView = [[UIImageView alloc] init];
146 | _coverImageView.userInteractionEnabled = YES;
147 | _coverImageView.tag = 100;
148 | _coverImageView.contentMode = UIViewContentModeScaleAspectFit;
149 | }
150 | return _coverImageView;
151 | }
152 |
153 | - (UIImageView *)bgImgView {
154 | if (!_bgImgView) {
155 | _bgImgView = [[UIImageView alloc] init];
156 | _bgImgView.userInteractionEnabled = YES;
157 | }
158 | return _bgImgView;
159 | }
160 |
161 | - (UIView *)effectView {
162 | if (!_effectView) {
163 | if (@available(iOS 8.0, *)) {
164 | UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
165 | _effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
166 | } else {
167 | UIToolbar *effectView = [[UIToolbar alloc] init];
168 | effectView.barStyle = UIBarStyleBlackTranslucent;
169 | _effectView = effectView;
170 | }
171 | }
172 | return _effectView;
173 | }
174 |
175 | @end
176 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/ViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface ViewController : UIViewController
12 |
13 |
14 | @end
15 |
16 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/ViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import "ViewController.h"
10 | #import "MPListViewController.h"
11 | #import "MPDetailViewController.h"
12 | #import "MPWaterFallViewController.h"
13 | #import "MPCardLayoutViewController.h"
14 |
15 | @interface ViewController ()
16 |
17 | @property (nonatomic, strong) UITableView *tableView;
18 | @property (nonatomic, copy) NSArray *controllerArr;
19 | @property (nonatomic, copy) NSArray *demoName;
20 |
21 | @end
22 |
23 | @implementation ViewController
24 |
25 | - (void)viewDidLoad {
26 | [super viewDidLoad];
27 | self.controllerArr = @[
28 | @"MPListViewController",
29 | @"MPDetailViewController",
30 | @"MPWaterFallViewController",
31 | @"MPCardLayoutViewController",
32 | @"MPUserDynamicViewController"
33 | ];
34 | self.demoName = @[
35 | @"预加载-列表播放-无缝续播",
36 | @"预加载-抖音列表",
37 | @"瀑布流列表-转场动画演示",
38 | @"卡片布局演示",
39 | @"转场动画手势退出"
40 | ];
41 |
42 | self.tableView = [[UITableView alloc] init];
43 | self.tableView.delegate = self;
44 | self.tableView.dataSource = self;
45 | self.tableView.frame = self.view.bounds;
46 | [self.view addSubview:self.tableView];
47 | }
48 |
49 | // MARK: - UITableViewDelegate, UITableViewDataSource
50 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
51 | {
52 | return self.demoName.count;
53 | }
54 |
55 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
56 | {
57 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ID"];
58 | if (!cell) {
59 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"ID"];
60 | }
61 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
62 | cell.textLabel.text = self.demoName[indexPath.row];
63 | return cell;
64 | }
65 |
66 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
67 | {
68 | Class cl = NSClassFromString(self.controllerArr[indexPath.row]);
69 | UIViewController *vc = [[cl alloc] init];
70 | [self.navigationController pushViewController:vc animated:YES];
71 | }
72 |
73 | @end
74 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/WaterFall/MPWaterFallLayout.h:
--------------------------------------------------------------------------------
1 | //
2 | // MPWaterFallLayout.h
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/1/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class MPWaterFallLayout;
12 |
13 | @protocol MPWaterFallLayoutDataSource
14 |
15 | @required
16 | /// 获取item高度,返回itemWidth和indexPath去获取
17 | - (CGFloat)waterFallLayout: (MPWaterFallLayout *)layout itemHeightForItemWidth: (CGFloat)itemWidth atIndexPath: (NSIndexPath *)indexPath;
18 |
19 | @end
20 |
21 | @interface MPWaterFallLayout : UICollectionViewLayout
22 |
23 | @property (nonatomic, weak) id dataSource;
24 | /// 根据设置的列数,列间距,返回itemWidth
25 | @property (nonatomic, readonly) CGFloat itemWidth;
26 | /// 总共有多少列,默认为2
27 | @property (nonatomic) NSInteger column;
28 | /// 列间距,默认为0
29 | @property (nonatomic) CGFloat columnSpacing;
30 | /// 行间距
31 | @property (nonatomic) CGFloat rowSpacing;
32 | /// section与collectionView的间距,默认是(0,0,0,0)
33 | @property (nonatomic) UIEdgeInsets sectionInset;
34 |
35 | + (instancetype)waterFallLayoutWithColumn:(NSInteger)column;
36 | - (instancetype)initWIthColumn:(NSInteger)column;
37 |
38 |
39 | @end
40 |
41 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/WaterFall/MPWaterFallLayout.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPWaterFallLayout.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2020/1/16.
6 | // Copyright © 2020 Maple. All rights reserved.
7 | //
8 |
9 | #import "MPWaterFallLayout.h"
10 |
11 | @interface MPWaterFallLayout ()
12 | /// 用于记录每一列的最大Y值
13 | @property (nonatomic, strong) NSMutableDictionary *maxYDic;
14 | /// attributes数组
15 | @property (nonatomic, strong) NSMutableArray *attributesArray;
16 |
17 | @end
18 |
19 | @implementation MPWaterFallLayout
20 |
21 | + (instancetype)waterFallLayoutWithColumn:(NSInteger)column
22 | {
23 | return [[self alloc] initWIthColumn:column];
24 | }
25 |
26 | - (instancetype)init
27 | {
28 | if (self = [super init]) {
29 | self.column = 2;
30 | }
31 | return self;
32 | }
33 |
34 | - (instancetype)initWIthColumn:(NSInteger)column
35 | {
36 | if (self = [super init]) {
37 | self.column = column;
38 | }
39 | return self;
40 | }
41 |
42 | // MARK: - Layout Override 布局必须重写的方法
43 | /// 1、初始化数据源
44 | - (void)prepareLayout
45 | {
46 | [super prepareLayout];
47 | [self.attributesArray removeAllObjects];
48 | // 初始化字典,有几列就有几个键值对,key为列,value为列的最大y值,
49 | // 初始值为上内边距
50 | for (int i = 0; i < self.column; i++) {
51 | self.maxYDic[@(i)] = @(self.sectionInset.top);
52 | }
53 | // 获取item总数
54 | NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
55 |
56 | // 为每个Item创建attributes存入数组中
57 | for (int i = 0; i < itemCount; i++) {
58 | // 循环调用2去计算item attribute
59 | UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
60 | [self.attributesArray addObject:attributes];
61 | }
62 | }
63 |
64 | /// 2、计算每个Attribute
65 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
66 | {
67 | UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
68 | NSAssert([self.dataSource respondsToSelector:@selector(waterFallLayout:itemHeightForItemWidth:atIndexPath:)], @"you must override waterFallLayout:itemHeightForItemWidth:atIndexPath: methods - Warning : 需要重写瀑布流的返回高度代理方法!");
69 | CGFloat itemWidth = self.itemWidth;
70 | CGFloat itemHeight = [self.dataSource waterFallLayout:self itemHeightForItemWidth:itemWidth atIndexPath:attributes.indexPath];
71 |
72 | /// 找出最短的一列
73 | __block NSNumber *minIndex = @(0);
74 | [self.maxYDic enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL * _Nonnull stop) {
75 | if ([self.maxYDic[minIndex] floatValue] > [obj floatValue]) {
76 | minIndex = key;
77 | }
78 | }];
79 | // 根据最短列去计算itemX
80 | CGFloat itemX = self.sectionInset.left + (self.columnSpacing + itemWidth) * minIndex.intValue;
81 | CGFloat itemY = 0;
82 | if (self.column == 1) {
83 | // 一列情况
84 | if (indexPath.row == 0 ) {
85 | itemY = [self.maxYDic[minIndex] floatValue];
86 | }else{
87 | itemY = [self.maxYDic[minIndex] floatValue] + self.rowSpacing;
88 | }
89 | }else{
90 | // 瀑布流多列情况
91 | // 第一行Cell不需要添加RowSpacing, 对应的indexPath.row = 0 && =1;
92 | if (indexPath.row == 0 || indexPath.row == 1) {
93 | itemY = [self.maxYDic[minIndex] floatValue];
94 | }else{
95 | itemY = [self.maxYDic[minIndex] floatValue] + self.rowSpacing;
96 | }
97 | }
98 | attributes.frame = CGRectMake(itemX, itemY , itemWidth, itemHeight);
99 | // 更新maxY
100 | self.maxYDic[minIndex] = @(CGRectGetMaxY(attributes.frame));
101 | return attributes;
102 | }
103 |
104 | /// 3、返回数据源
105 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
106 | {
107 | return self.attributesArray;
108 | }
109 |
110 | /// 4、返回itemSize
111 | - (CGSize)collectionViewContentSize
112 | {
113 | __block NSNumber *maxIndex = @(0);
114 | // 找到最长的一列
115 | [self.maxYDic enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL * _Nonnull stop) {
116 | if ([self.maxYDic[maxIndex] floatValue] < [obj floatValue]) {
117 | maxIndex = key;
118 | }
119 | }];
120 | CGFloat contentSizeY = [self.maxYDic[maxIndex] floatValue] + self.sectionInset.bottom;
121 | return CGSizeMake(self.collectionView.frame.size.width, contentSizeY);
122 | }
123 |
124 | // MARK: - Getter & Setter
125 | - (NSMutableDictionary *)maxYDic
126 | {
127 | if (!_maxYDic) {
128 | _maxYDic = [[NSMutableDictionary alloc] init];
129 | }
130 | return _maxYDic;
131 | }
132 |
133 | - (NSMutableArray *)attributesArray
134 | {
135 | if (!_attributesArray) {
136 | _attributesArray = [NSMutableArray array];
137 | }
138 | return _attributesArray;
139 | }
140 |
141 | - (CGFloat)itemWidth
142 | {
143 | CGFloat collectionViewWidth = self.collectionView.frame.size.width;
144 | if (collectionViewWidth == 0) {
145 | collectionViewWidth = [UIScreen mainScreen].bounds.size.width;
146 | }
147 | CGFloat itemWidth = (collectionViewWidth - self.sectionInset.left - self.sectionInset.right - (self.column) * self.columnSpacing * (self.column - 1)) / self.column;
148 | return itemWidth;
149 | }
150 |
151 | @end
152 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemo/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // MPPlayerDemo
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | NSString * appDelegateClassName;
14 | @autoreleasepool {
15 | // Setup code that might create autoreleased objects goes here.
16 | appDelegateClassName = NSStringFromClass([AppDelegate class]);
17 | }
18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName);
19 | }
20 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemoTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemoTests/MPPlayerDemoTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPPlayerDemoTests.m
3 | // MPPlayerDemoTests
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MPPlayerDemoTests : XCTestCase
12 |
13 | @end
14 |
15 | @implementation MPPlayerDemoTests
16 |
17 | - (void)setUp {
18 | // Put setup code here. This method is called before the invocation of each test method in the class.
19 | }
20 |
21 | - (void)tearDown {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | - (void)testExample {
26 | // This is an example of a functional test case.
27 | // Use XCTAssert and related functions to verify your tests produce the correct results.
28 | }
29 |
30 | - (void)testPerformanceExample {
31 | // This is an example of a performance test case.
32 | [self measureBlock:^{
33 | // Put the code you want to measure the time of here.
34 | }];
35 | }
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemoUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/MPPlayerDemo/MPPlayerDemoUITests/MPPlayerDemoUITests.m:
--------------------------------------------------------------------------------
1 | //
2 | // MPPlayerDemoUITests.m
3 | // MPPlayerDemoUITests
4 | //
5 | // Created by Maple on 2019/12/27.
6 | // Copyright © 2019 Maple. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface MPPlayerDemoUITests : XCTestCase
12 |
13 | @end
14 |
15 | @implementation MPPlayerDemoUITests
16 |
17 | - (void)setUp {
18 | // Put setup code here. This method is called before the invocation of each test method in the class.
19 |
20 | // In UI tests it is usually best to stop immediately when a failure occurs.
21 | self.continueAfterFailure = NO;
22 |
23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
24 | }
25 |
26 | - (void)tearDown {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | }
29 |
30 | - (void)testExample {
31 | // UI tests must launch the application that they test.
32 | XCUIApplication *app = [[XCUIApplication alloc] init];
33 | [app launch];
34 |
35 | // Use recording to get started writing UI tests.
36 | // Use XCTAssert and related functions to verify your tests produce the correct results.
37 | }
38 |
39 | - (void)testLaunchPerformance {
40 | if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
41 | // This measures how long it takes to launch your application.
42 | [self measureWithMetrics:@[XCTOSSignpostMetric.applicationLaunchMetric] block:^{
43 | [[[XCUIApplication alloc] init] launch];
44 | }];
45 | }
46 | }
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/MPPlayerDemo/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'MPPlayerDemo' do
5 | # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
6 | # use_frameworks!
7 |
8 | # Pods for MPPlayerDemo
9 | pod 'ZFPlayer'
10 | pod 'KTVHTTPCache'
11 | pod 'ZFPlayer/ControlView'
12 |
13 | target 'MPPlayerDemoTests' do
14 | inherit! :search_paths
15 | # Pods for testing
16 | end
17 |
18 | target 'MPPlayerDemoUITests' do
19 | inherit! :search_paths
20 | # Pods for testing
21 | end
22 |
23 | end
24 |
--------------------------------------------------------------------------------
/MPPlayerDemo/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - CocoaAsyncSocket (7.6.4)
3 | - KTVCocoaHTTPServer (1.0.0):
4 | - CocoaAsyncSocket
5 | - KTVHTTPCache (2.0.1):
6 | - KTVCocoaHTTPServer
7 | - ZFPlayer (3.3.0):
8 | - ZFPlayer/Core (= 3.3.0)
9 | - ZFPlayer/ControlView (3.3.0):
10 | - ZFPlayer/Core
11 | - ZFPlayer/Core (3.3.0)
12 |
13 | DEPENDENCIES:
14 | - KTVHTTPCache
15 | - ZFPlayer
16 | - ZFPlayer/ControlView
17 |
18 | SPEC REPOS:
19 | trunk:
20 | - CocoaAsyncSocket
21 | - KTVCocoaHTTPServer
22 | - KTVHTTPCache
23 | - ZFPlayer
24 |
25 | SPEC CHECKSUMS:
26 | CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
27 | KTVCocoaHTTPServer: df8d7b861e603ff8037e9b2138aca2563a6b768d
28 | KTVHTTPCache: 588c3eb16f6bd1e6fde1e230dabfb7bd4e490a4d
29 | ZFPlayer: 67de190f7aaad8ef4713aa2aef4ae170c663593d
30 |
31 | PODFILE CHECKSUM: eb64412a015159356b04c6cf9f71373bd54bf10e
32 |
33 | COCOAPODS: 1.8.4
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #Demo演示的功能
2 | `提示:文末有相关的Demo下载链接`
3 | * ZFPlayer的列表播放
4 | * 使用KTVHTTPCache实现缓存(播放过的视频无需再下载)
5 | * 使用KTVHTTPCache实现预加载(可以实现秒播)
6 | * 自定义转场动画(实现无缝衔接的播放效果)
7 | * 瀑布流页面(双排列表展示,以及转场动画)
8 |
9 | gif演示:
10 | 
11 | gif看不到的,可以到简书上看
12 | https://www.jianshu.com/p/3ffb26bf862c
13 |
14 |
15 | # 一、缓存+预加载功能
16 |
17 | ## 1、播放器mgr核心代码
18 | mgr实现ZFPlayerMediaPlayback协议,然后在初始化时,开启本地服务器
19 | ```
20 | + (void)initialize
21 | {
22 | [KTVHTTPCache logSetConsoleLogEnable:NO];
23 | NSError *error = nil;
24 | [KTVHTTPCache proxyStart:&error];
25 | if (error) {
26 | NSLog(@"Proxy Start Failure, %@", error);
27 | }
28 | [KTVHTTPCache encodeSetURLConverter:^NSURL *(NSURL *URL) {
29 | // NSLog(@"URL Filter reviced URL : %@", URL);
30 | return URL;
31 | }];
32 | [KTVHTTPCache downloadSetUnacceptableContentTypeDisposer:^BOOL(NSURL *URL, NSString *contentType) {
33 | return NO;
34 | }];
35 | // 设置缓存最大容量
36 | [KTVHTTPCache cacheSetMaxCacheLength:1024 * 1024 * 1024];
37 | }
38 | ```
39 | 设置assetURL时,设置KTVHTTpCache为中间服务器,若该资源已缓存完毕,就无需代理,这个判断可以使已缓存的视频播放的更快
40 | ```
41 | - (void)setAssetURL:(NSURL *)assetURL {
42 | if (self.player) [self stop];
43 | // 如果有缓存,直接取本地缓存
44 | NSURL *url = [KTVHTTPCache cacheCompleteFileURLWithURL:assetURL];
45 | if (url) {
46 | _assetURL = url;
47 | }else {
48 | // 设置代理
49 | _assetURL = [KTVHTTPCache proxyURLWithOriginalURL:assetURL];
50 | }
51 | [self prepareToPlay];
52 | }
53 | ```
54 |
55 |
56 | ##2、播放器Player核心代码
57 | 创建playableProtocol,方便数据管理
58 | ```
59 | /// 只有实现该协议的模型才能预加载
60 | @protocol XSTPlayable
61 | /// string 视频链接
62 | @property (nonatomic, copy) NSString *video_url;
63 | @end
64 | ```
65 |
66 | 核心播放器为ZFPlayerController,为了方便管理,我们创建一个中间类包裹ZFPlayerController,且增加可以设置的预加载属性
67 | ```
68 | @interface MPPlayerController : NSObject
69 |
70 | // 预加载上几条
71 | @property (nonatomic, assign) NSUInteger preLoadNum;
72 | /// 预加载下几条
73 | @property (nonatomic, assign) NSUInteger nextLoadNum;
74 | /// 预加载的的百分比,默认10%
75 | @property (nonatomic, assign) double preloadPrecent;
76 | /// 设置playableAssets后,马上预加载的条数
77 | @property (nonatomic, assign) NSUInteger initPreloadNum;
78 | /// set之后,先预加载几个
79 | @property (nonatomic, copy) NSArray> *playableArray;
80 | ....
81 | ```
82 |
83 |
84 | ##3、预加载核心代码
85 | 预加载的时机是当前视频可以播放了,才进行预加载
86 | ```
87 | - (void)playTheIndexPath:(NSIndexPath *)indexPath playable: (id)playable
88 | {
89 | // 播放前,先停止所有的预加载任务
90 | [self cancelAllPreload];
91 | _currentPlayable = playable;
92 | [self.player playTheIndexPath:indexPath assetURL:[NSURL URLWithString:playable.video_url] scrollToTop:NO];
93 | __weak typeof(self) weakSelf = self;
94 | self.playerReadyToPlay = ^(id _Nonnull asset, NSURL * _Nonnull assetURL) {
95 | [weakSelf preload: playable];
96 | };
97 | }
98 | ```
99 | 预加载的规则是预加载当前视频的上2个,和下2个视频,逐个开启预加载,视频预加载(`核心类KTVHCDataLoader`)到10%就停止,然后开始下一个视频的预加载。这里要注意异步线程的操作,要加锁处理
100 | ```
101 | /// 根据传入的模型,预加载上几个,下几个的视频
102 | - (void)preload: (id)resource
103 | {
104 | if (self.playableArray.count <= 1)
105 | return;
106 | if (_nextLoadNum == 0 && _preLoadNum == 0)
107 | return;
108 | NSInteger start = [self.playableArray indexOfObject:resource];
109 | if (start == NSNotFound)
110 | return;
111 | [self cancelAllPreload];
112 | NSInteger index = 0;
113 | for (NSInteger i = start + 1; i < self.playableArray.count && index < _nextLoadNum; i++)
114 | {
115 | index += 1;
116 | id model = self.playableArray[i];
117 | XSTPreLoaderModel *preModel = [self getPreloadModel: model.video_url];
118 | if (preModel) {
119 | @synchronized (self.preloadArr) {
120 | [self.preloadArr addObject: preModel];
121 | }
122 | }
123 | }
124 | index = 0;
125 | for (NSInteger i = start - 1; i >= 0 && index < _preLoadNum; i--)
126 | {
127 | index += 1;
128 | id model = self.playableArray[i];
129 | XSTPreLoaderModel *preModel = [self getPreloadModel: model.video_url];
130 | if (preModel) {
131 | @synchronized (self.preloadArr) {
132 | [self.preloadArr addObject:preModel];
133 | }
134 | }
135 | }
136 | [self processLoader];
137 | }
138 | /// 取消所有的预加载
139 | - (void)cancelAllPreload
140 | {
141 | @synchronized (self.preloadArr) {
142 | if (self.preloadArr.count == 0)
143 | {
144 | return;
145 | }
146 | [self.preloadArr enumerateObjectsUsingBlock:^(XSTPreLoaderModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
147 | [obj.loader close];
148 | }];
149 | [self.preloadArr removeAllObjects];
150 | }
151 | }
152 |
153 | - (XSTPreLoaderModel *)getPreloadModel: (NSString *)urlStr
154 | {
155 | if (!urlStr)
156 | return nil;
157 | // 判断是否已在队列中
158 | __block Boolean res = NO;
159 | @synchronized (self.preloadArr) {
160 | [self.preloadArr enumerateObjectsUsingBlock:^(XSTPreLoaderModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
161 | if ([obj.url isEqualToString:urlStr])
162 | {
163 | res = YES;
164 | *stop = YES;
165 | }
166 | }];
167 | }
168 | if (res)
169 | return nil;
170 | NSURL *proxyUrl = [KTVHTTPCache proxyURLWithOriginalURL: [NSURL URLWithString:urlStr]];
171 | KTVHCDataCacheItem *item = [KTVHTTPCache cacheCacheItemWithURL:proxyUrl];
172 | double cachePrecent = 1.0 * item.cacheLength / item.totalLength;
173 | // 判断缓存已经超过10%了
174 | if (cachePrecent >= self.preloadPrecent)
175 | return nil;
176 | KTVHCDataRequest *req = [[KTVHCDataRequest alloc] initWithURL:proxyUrl headers:[NSDictionary dictionary]];
177 | KTVHCDataLoader *loader = [KTVHTTPCache cacheLoaderWithRequest:req];
178 | XSTPreLoaderModel *preModel = [[XSTPreLoaderModel alloc] initWithURL:urlStr loader:loader];
179 | return preModel;
180 | }
181 |
182 | - (void)processLoader
183 | {
184 | @synchronized (self.preloadArr) {
185 | if (self.preloadArr.count == 0)
186 | return;
187 | XSTPreLoaderModel *model = self.preloadArr.firstObject;
188 | model.loader.delegate = self;
189 | [model.loader prepare];
190 | }
191 | }
192 |
193 | /// 根据loader,移除预加载任务
194 | - (void)removePreloadTask: (KTVHCDataLoader *)loader
195 | {
196 | @synchronized (self.preloadArr) {
197 | XSTPreLoaderModel *target = nil;
198 | for (XSTPreLoaderModel *model in self.preloadArr) {
199 | if ([model.loader isEqual:loader])
200 | {
201 | target = model;
202 | break;
203 | }
204 | }
205 | if (target)
206 | [self.preloadArr removeObject:target];
207 | }
208 | }
209 |
210 | // MARK: - KTVHCDataLoaderDelegate
211 | - (void)ktv_loaderDidFinish:(KTVHCDataLoader *)loader
212 | {
213 | }
214 | - (void)ktv_loader:(KTVHCDataLoader *)loader didFailWithError:(NSError *)error
215 | {
216 | // 若预加载失败的话,就直接移除任务,开始下一个预加载任务
217 | [self removePreloadTask:loader];
218 | [self processLoader];
219 | }
220 | - (void)ktv_loader:(KTVHCDataLoader *)loader didChangeProgress:(double)progress
221 | {
222 | if (progress >= self.preloadPrecent)
223 | {
224 | [loader close];
225 | [self removePreloadTask:loader];
226 | [self processLoader];
227 | }
228 | }
229 | ```
230 |
231 | # 二、无缝衔接转场动画
232 | 这里我直接拿ZFPlayerDemo中的一个列表播放,一个抖音列表播放的例子进行演示,不熟悉转场动画的,建议自行先看看唐巧的https://blog.devtang.com/2016/03/13/iOS-transition-guide/了解,这里不多说,直接上核心代码。
233 |
234 | 1、首先必须实现代理`UINavigationControllerDelegate`
235 | ```
236 | @interface MPDetailViewController : UIViewController
237 | ```
238 |
239 | 2、传递player,startView,startImage等,并实现popback回调
240 | ```
241 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
242 | {
243 | ZFTableViewCell *cell = (ZFTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
244 | NSIndexPath *currentIndexPath = [self.tableView indexPathForCell:cell];
245 | // 点击的不是正在播放的cell,就先播放再跳转
246 | if ([currentIndexPath compare:self.tableView.zf_playingIndexPath] != NSOrderedSame) {
247 | [self.player stopCurrentPlayingCell];
248 | self.tableView.zf_playingIndexPath = currentIndexPath;
249 | [self playTheVideoAtIndexPath:currentIndexPath scrollToTop:NO];
250 | [self.player.currentPlayerManager.view layoutIfNeeded];
251 | }
252 | self.tableView.zf_playingIndexPath = currentIndexPath;
253 |
254 | MPDetailViewController *vc = [[MPDetailViewController alloc] init];
255 | vc.player = self.player;
256 | vc.index = indexPath.row;
257 | vc.startImage = cell.coverImageView.image;
258 | vc.startView = cell.coverImageView;
259 | vc.dataSource = [self.playableArray mutableCopy];
260 | @weakify(self)
261 | vc.popbackBlock = ^{
262 | @strongify(self)
263 | [self.player updateScrollViewPlayerToCell];
264 | [self.player.currentPlayerManager play];
265 | };
266 | self.navigationController.delegate = vc;
267 | [self.navigationController pushViewController:vc animated:YES];
268 | }
269 | ```
270 |
271 | 3、实现`UIViewControllerAnimatedTransitioning`协议
272 | ```
273 | /// 用于视频信息流的转场动画
274 | @interface MPTransition : NSObject
275 |
276 | /**
277 | 初始化动画
278 |
279 | @param duration 动画时长
280 | @param startView 开始视图
281 | @param startImage 开始图片
282 | @param player 播放器
283 | @param operation 动画形式
284 | @param completion 动画完成block
285 | @return 动画实例
286 | */
287 | + (instancetype)animationWithDuration:(NSTimeInterval)duration
288 | startView:(UIView *)startView
289 | startImage:(UIImage *)startImage
290 | player: (MPPlayerController *)player
291 | operation:(UINavigationControllerOperation)operation
292 | completion:(void(^)(void))completion;
293 |
294 | @end
295 | ```
296 | 4、分别实现push,pop的转场动画
297 | ```
298 | @interface MPTransition()
299 |
300 | @property (nonatomic, strong) UIView *startView;
301 | @property (nonatomic, strong) UIImage *startImage;
302 | @property (nonatomic, assign) NSTimeInterval duration;
303 | @property (nonatomic, strong) MPPlayerController *player;
304 | @property (nonatomic, assign) UINavigationControllerOperation operation;
305 | @property (nonatomic, assign) void(^completion)(void);
306 | @property (nonatomic, strong) UIView *effectView;
307 |
308 | @end
309 |
310 | @implementation MPTransition
311 |
312 | + (instancetype)animationWithDuration:(NSTimeInterval)duration
313 | startView:(UIView *)startView
314 | startImage:(UIImage *)startImage
315 | player: (MPPlayerController *)player
316 | operation:(UINavigationControllerOperation)operation
317 | completion:(void(^)(void))completion
318 | {
319 | MPTransition *animation = [MPTransition new];
320 | animation.player = player;
321 | animation.duration = duration;
322 | animation.startView = startView;
323 | animation.startImage = startImage;
324 | animation.operation = operation;
325 | animation.completion = completion;
326 |
327 | return animation;
328 | }
329 |
330 | - (void)animateTransition:(id)transitionContext {
331 | if (self.operation == UINavigationControllerOperationPush) {
332 | [self startPushAnimation: transitionContext];
333 | }else {
334 | [self startPopAnimation: transitionContext];
335 | }
336 | }
337 |
338 | - (void)startPushAnimation:(id)transitionContext
339 | {
340 | // 获取 fromView 和 toView
341 | UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
342 | UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
343 |
344 | // 添加到动画容器视图中
345 | [[transitionContext containerView] addSubview:fromView];
346 | [[transitionContext containerView] addSubview:toView];
347 |
348 | UIImageView *bgImgView = [[UIImageView alloc] initWithFrame:fromView.bounds];
349 | bgImgView.contentMode = UIViewContentModeScaleAspectFill;
350 | bgImgView.image = self.startImage;
351 | UIView *colorCover = [[UIView alloc] init];
352 | colorCover.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4];
353 | colorCover.frame = fromView.bounds;
354 | [bgImgView addSubview:colorCover];
355 | [bgImgView addSubview:self.effectView];
356 | [[transitionContext containerView] addSubview:bgImgView];
357 |
358 | // 创建player容器
359 | CGRect winFrame = CGRectZero;
360 | if (self.startView) {
361 | winFrame = [self.startView convertRect:self.startView.bounds toView:nil];
362 | }
363 |
364 | UIImageView *playerContainer = [[UIImageView alloc] initWithFrame:winFrame];
365 | playerContainer.image = self.startImage;
366 | playerContainer.contentMode = UIViewContentModeScaleAspectFit;
367 | [[transitionContext containerView] addSubview:playerContainer];
368 | if (self.player) {
369 | self.player.currentPlayerManager.scalingMode = self.player.videoFlowScalingMode;
370 | self.player.currentPlayerManager.view.backgroundColor = [UIColor clearColor];
371 | [self.player updateNoramlPlayerWithContainerView:playerContainer];
372 | }
373 | CGFloat bottomOffset = iPhoneX ? 83 : 0;
374 | NSTimeInterval duration = [self transitionDuration:transitionContext];
375 | CGRect targetFrame = CGRectMake(0, 0, ZFPlayer_ScreenWidth, ZFPlayer_ScreenHeight - bottomOffset);
376 |
377 | toView.alpha = 0.0f;
378 | bgImgView.alpha = 0;
379 | [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
380 | // mask 渐变效果
381 | bgImgView.alpha = 1;
382 | playerContainer.frame = targetFrame;
383 | } completion:^(BOOL finished) {
384 | toView.alpha = 1.0f;
385 | // 移除临时视图
386 | [bgImgView removeFromSuperview];
387 | [playerContainer removeFromSuperview];
388 | // 结束动画
389 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
390 | if (self.completion) {
391 | self.completion();
392 | }
393 | }];
394 | }
395 |
396 | - (void)startPopAnimation: (id)transitionContext
397 | {
398 | // 获取 fromView 和 toView
399 | UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
400 | UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
401 | self.player.currentPlayerManager.view.backgroundColor = [UIColor blackColor];
402 |
403 | // 添加到动画容器视图中
404 | UIView *container = [transitionContext containerView];
405 | [container addSubview:toView];
406 | [container addSubview:fromView];
407 | container.backgroundColor = [UIColor clearColor];
408 |
409 | // 添加动画临时视图到 fromView
410 | CGFloat bottomOffset = iPhoneX ? 83 : 0;
411 | CGRect normalFrame = CGRectMake(0, 0, ZFPlayer_ScreenWidth, ZFPlayer_ScreenHeight - bottomOffset);
412 | CGRect winFrame = CGRectZero;
413 | if (self.startView) {
414 | winFrame = [self.startView convertRect:self.startView.bounds toView:nil];
415 | }
416 |
417 | // 显示图片
418 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:normalFrame];
419 | imageView.contentMode = UIViewContentModeScaleAspectFit;
420 | imageView.clipsToBounds = YES;
421 | imageView.image = self.startImage;
422 | if (self.player) {
423 | // pop回去的时候,设置回原来的scalingMode
424 | self.player.currentPlayerManager.scalingMode = ZFPlayerScalingModeAspectFill;
425 | [self.player updateNoramlPlayerWithContainerView:imageView];
426 | }
427 |
428 | [container addSubview:imageView];
429 |
430 | toView.alpha = 1;
431 | fromView.alpha = 1;
432 | NSTimeInterval duration = [self transitionDuration:transitionContext];
433 |
434 | [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
435 | imageView.frame = winFrame;
436 | fromView.alpha = 0;
437 | } completion:^(BOOL finished) {
438 | // 移除临时视图
439 | [imageView removeFromSuperview];
440 |
441 | // 结束动画
442 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
443 |
444 | if (self.completion) {
445 | self.completion();
446 | }
447 | }];
448 | }
449 |
450 | - (NSTimeInterval)transitionDuration:(id)transitionContext {
451 | return self.duration;
452 | }
453 |
454 | - (UIView *)effectView {
455 | if (!_effectView) {
456 | if (@available(iOS 8.0, *)) {
457 | UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
458 | _effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
459 | } else {
460 | UIToolbar *effectView = [[UIToolbar alloc] init];
461 | effectView.barStyle = UIBarStyleBlackTranslucent;
462 | _effectView = effectView;
463 | }
464 | }
465 | return _effectView;
466 | }
467 |
468 | @end
469 |
470 | ```
471 |
472 | #三、相关链接
473 | * 瀑布流
474 | https://www.jianshu.com/p/7976739e9034
475 | * Demo链接
476 | https://github.com/maple1994/MPPlayerDemo
477 | * ZFPlayer
478 | https://github.com/renzifeng/ZFPlayer
479 | * KTVHttpCache
480 | https://github.com/ChangbaDevs/KTVHTTPCache
481 | * 转场动画
482 | https://blog.devtang.com/2016/03/13/iOS-transition-guide/
483 |
--------------------------------------------------------------------------------
/playerDemo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maple1994/MPPlayerDemo/5d7cb8d8310a915df2856c4cc3e2881c3f9408a1/playerDemo.gif
--------------------------------------------------------------------------------