├── .gitignore
├── .travis.yml
├── KSYAirStreamer.podspec
├── README.md
├── demo
├── KSYAirStreamer.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── KSYAirStreamer-Example.xcscheme
├── KSYAirStreamer
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Images.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-57x57@1x.png
│ │ │ ├── Icon-App-57x57@2x.png
│ │ │ ├── Icon-App-60x60@1x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-72x72@1x.png
│ │ │ ├── Icon-App-72x72@2x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-76x76@3x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ ├── Icon-Small-50x50@1x.png
│ │ │ └── Icon-Small-50x50@2x.png
│ ├── KSYAirStreamer-Info.plist
│ ├── KSYAirStreamer-Prefix.pch
│ ├── KSYAirView.h
│ ├── KSYAirView.m
│ ├── KSYAppDelegate.h
│ ├── KSYAppDelegate.m
│ ├── KSYBitStreamDumpVC.h
│ ├── KSYBitStreamDumpVC.m
│ ├── KSYUIUtils
│ │ ├── KSYDrawingView.h
│ │ ├── KSYDrawingView.m
│ │ ├── KSYFileSelector.h
│ │ ├── KSYFileSelector.m
│ │ ├── KSYNameSlider.h
│ │ ├── KSYNameSlider.m
│ │ ├── KSYUIVC.h
│ │ ├── KSYUIVC.m
│ │ ├── KSYUIView.h
│ │ └── KSYUIView.m
│ ├── KSYViewController.h
│ ├── KSYViewController.m
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ └── main.m
├── Podfile
└── Tests
│ ├── Tests-Info.plist
│ ├── Tests-Prefix.pch
│ ├── Tests.m
│ └── en.lproj
│ └── InfoPlist.strings
├── docs
└── html
│ ├── Classes
│ ├── KSYAirStreamKit.html
│ ├── KSYAirTunesConfig.html
│ ├── KSYAirTunesServer.html
│ └── KSYAudioCap.html
│ ├── Constants
│ ├── KSYAirErrorCode.html
│ ├── KSYAirState.html
│ └── KSYAirVideoDecoder.html
│ ├── Protocols
│ └── KSYAirDelegate.html
│ ├── css
│ ├── scss
│ │ ├── _index.scss
│ │ ├── _layout.scss
│ │ ├── _normalize.scss
│ │ ├── _object.scss
│ │ ├── _print.scss
│ │ ├── _variables.scss
│ │ ├── _xcode.scss
│ │ └── style.scss
│ └── style.css
│ ├── hierarchy.html
│ ├── img
│ ├── button_bar_background.png
│ ├── disclosure.png
│ ├── disclosure_open.png
│ ├── library_background.png
│ └── title_background.png
│ ├── index.html
│ └── js
│ └── script.js
├── prebuilt
├── include
│ └── KSYAirStreamer
│ │ └── KSYAirTunesServer.h
└── libs
│ └── libksyairserver.a
└── source
├── KSYAirStreamKit.h
└── KSYAirStreamKit.m
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/osx
2 |
3 | ### OSX ###
4 | .DS_Store
5 | .AppleDouble
6 | .LSOverride
7 |
8 | # Icon must end with two \r
9 | Icon
10 |
11 |
12 | # Thumbnails
13 | ._*
14 |
15 | # Files that might appear in the root of a volume
16 | .DocumentRevisions-V100
17 | .fseventsd
18 | .Spotlight-V100
19 | .TemporaryItems
20 | .Trashes
21 | .VolumeIcon.icns
22 |
23 | # Directories potentially created on remote AFP share
24 | .AppleDB
25 | .AppleDesktop
26 | Network Trash Folder
27 | Temporary Items
28 | .apdisk
29 |
30 | lib/
31 | *.ipa
32 | xcuserdata
33 | project.xcworkspace/
34 |
35 | framework/
36 | build/
37 | *.log
38 | Pods/
39 | xcshareddata/
40 | *.xcworkspace/
41 | Podfile.lock
42 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode8
3 | before_install:
4 | - pod repo update --silent
5 | podfile: demo/Podfile
6 | xcode_workspace: demo/KSYAirStreamer.xcworkspace
7 | xcode_scheme: KSYAirStreamer-Example
8 | script:
9 | - xcodebuild clean build -quiet -configuration Release -workspace demo/KSYAirStreamer.xcworkspace -scheme KSYAirStreamer-Example CODE_SIGNING_REQUIRED=NO
10 |
--------------------------------------------------------------------------------
/KSYAirStreamer.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'KSYAirStreamer'
3 | s.version = '1.5.0'
4 | s.summary = 'airplay receiver => rtmp streamer'
5 |
6 | s.description = <<-DESC
7 | * for iOS screen broadcast
8 | * airplay receiver to receive video frome an iOS devices
9 | * transcode video
10 | * publish rtmp stream
11 | DESC
12 |
13 | s.homepage = 'https://github.com/ksvc/KSYAirStreamer_iOS'
14 | s.license = {
15 | :type => 'Proprietary',
16 | :text => <<-LICENSE
17 | Copyright 2017 kingsoft Ltd. All rights reserved.
18 | LICENSE
19 | }
20 | s.author = { 'pengbins' => 'pengbin@kingsoft.com' }
21 | s.source = {
22 | :git => 'https://github.com/ksvc/KSYAirStreamer_iOS.git',
23 | :tag => 'v' + s.version.to_s
24 | }
25 | s.requires_arc = true
26 | s.ios.library = 'z', 'iconv', 'stdc++.6', 'bz2'
27 | s.ios.frameworks = [ 'AVFoundation', 'VideoToolbox', 'MediaPlayer']
28 | s.ios.deployment_target = '8.0'
29 | s.source_files = ['prebuilt/include/**/*.h','source/*.{h,m}']
30 | s.vendored_library = 'prebuilt/libs/libksyairserver.a'
31 | #s.vendored_library = 'prebuilt/libs/libksyairserver_auth.a'
32 | s.dependency 'libksygpulive/libksygpulive'
33 | s.dependency 'CocoaAsyncSocket'
34 | end
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [KSYAirStreamer iOS SDK](https://ksvc.github.io/KSYAirStreamer_iOS/docs/html/index.html)
2 |
3 | [](http://cocoapods.org/pods/KSYAirStreamer)[](http://cocoapods.org/pods/KSYAirStreamer)
4 |
5 |
6 | [](https://travis-ci.org/ksvc/KSYAirStreamer_iOS)
7 | [](https://cocoapods.org/pods/KSYAirStreamer)
8 | [](https://cocoapods.org/pods/KSYAirStreamer)
9 |
10 |
Source Type: Binary SDK
11 | Charge Type: nonfree
12 |
13 | ## 一. 功能特性
14 |
15 | [金山云录屏直播SDK][KSYAirStreamer]是金山云提供的直播解决方案的一部分,完成了iOS端全屏录制的功能,主要实现思路是本SDK内实现了一个Airplay的接收端, 开始录屏时iOS系统与SDK建立连接, SDK收到画面后, 编码发送到直播服务器. 其中编码和推流功能使用[金山云直播SDK][libksygpulive]实现.
16 |
17 | ![Airplay+live][airplaylive]
18 |
19 | 可以用于手游等直播录制场景。
20 |
21 | ### 1.1 录屏功能
22 | - [x] 支持iOS8/9/10/11录屏
23 | - [x] 硬解/软解支持
24 | - [x] 动态横竖屏支持
25 | - [x] 固定横屏/竖屏支持
26 |
27 | ### 1.2 关于上架
28 | 根据Apple的政策, 含有Airplay功能的APP无法通过App Store审查, 请注意.
29 |
30 | ### 1.3 费用
31 | 当前版本使用联网解密,因为网络原因或者服务器抖动,会导致录屏失败。当前评估版本可以免费集成和试用。
32 | 本评估版本不限制使用并发数。
33 |
34 | 请勿集成并上线,因为网络原因导致的功能不可用,[金山云][ksyun]不承担任何责任。
35 |
36 | #### 1.3.1 购买
37 |
38 | 如果需要商业使用,请登录[金山云魔方控制台][kmc]获取离线解密版本。离线版本使用过程中不会发生联网行为,可以稳定用于商业应用。
39 |
40 | 商业版本费用,请参考[金山云魔方控制台][kmc]报价。
41 |
42 | ## 二. SDK集成方法介绍
43 | ### 2.1 系统要求
44 | * 最低支持iOS版本:iOS 8.0
45 | * 最低支持iPhone型号:iPhone 5
46 | * 支持CPU架构: armv7,arm64(和i386,x86_64模拟器)
47 | * 含有i386和x86_64模拟器版本的库文件,录屏和推流功能无法在模拟器上工作
48 |
49 | ### 2.2 下载工程
50 | 本SDK 提供如下列出获取方式:
51 |
52 | #### 2.2.1 从[github](https://github.com/ksvc/KSYAirStreamer_iOS) clone
53 | 目录结构如下所示:
54 | - demo : demo工程演示本SDK的主要接口的使用
55 | - docs/html : appleDoc风格的网页版接口文档
56 | - prebuilt : 预编译库的头文件和库文件
57 | - source : 顶层kit类的源代码
58 |
59 | ```
60 | $ git clone https://github.com/ksvc/KSYAirStreamer_iOS.git --depth 1
61 | ```
62 |
63 | #### 2.2.3 使用Cocoapods 进行安装
64 | 通过Cocoapods 能将本SDK的静态库和代码下载到本地,只需要将类似如下语句中的一句加入你的Podfile:
65 | ```ruby
66 | pod 'libksygpulive', '~> 2.4.0'
67 | pod 'KSYAirStreamer', '~> 0.1.0'
68 | ```
69 |
70 | ### 2.3 开始运行demo工程
71 | demo 目录中已经有一个Podfile, 指定了本地开发版的pod
72 | 在demo目录下执行如下命令, 即可开始编译运行demo
73 | ```
74 | $ cd demo
75 | $ pod install
76 | $ open KSYAirStreamer.xcworkspace
77 | ```
78 |
79 | 注意:
80 | 1. 更新pod之后, 需要打开 xcwrokspace, 而不是xcodeproj
81 |
82 |
83 | ### 2.4 添加头文件到需要使用本SDK的文件中
84 | ```
85 | #import
86 | ```
87 |
88 | ### 2.5 SDK版本号查询
89 | 本SDK的版本号 主要通过头文件查询
90 | ```
91 | #define KSYAIRSTREAMER_VER 0.1.0
92 | ```
93 |
94 | ## 三. 参考文档
95 | * wiki: https://github.com/ksvc/KSYAirStreamer_iOS/wiki
96 | * API 文档: https://ksvc.github.io/KSYAirStreamer_iOS/html/index.html
97 |
98 | ## 四. 反馈与建议
99 | * 主页:[金山云](http://www.ksyun.com/)
100 | * 邮箱:
101 | * QQ讨论群:
102 | * 574179720 [视频云技术交流群]
103 | * 621137661 [视频云iOS技术交流]
104 | * 以上两个加一个QQ群即可
105 | * Issues:
106 |
107 |
108 |
109 | [libksygpulive]:https://github.com/ksvc/KSYLive_iOS
110 | [KSYAirStreamer]:https://github.com/ksvc/KSYAirStreamer_iOS
111 | [kmc]:https://kmc.console.ksyun.com/#sdk/KSYAirStreameriOS/fac09a
112 | [airplaylive]:https://ks3-cn-beijing.ksyun.com/ksy.vcloud.sdk/picture/airMirror/airplay_live.png
113 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer.xcodeproj/xcshareddata/xcschemes/KSYAirStreamer-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/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 |
27 |
28 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/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 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images":[
3 | {
4 | "idiom":"iphone",
5 | "size":"20x20",
6 | "scale":"2x",
7 | "filename":"Icon-App-20x20@2x.png"
8 | },
9 | {
10 | "idiom":"iphone",
11 | "size":"20x20",
12 | "scale":"3x",
13 | "filename":"Icon-App-20x20@3x.png"
14 | },
15 | {
16 | "idiom":"iphone",
17 | "size":"29x29",
18 | "scale":"1x",
19 | "filename":"Icon-App-29x29@1x.png"
20 | },
21 | {
22 | "idiom":"iphone",
23 | "size":"29x29",
24 | "scale":"2x",
25 | "filename":"Icon-App-29x29@2x.png"
26 | },
27 | {
28 | "idiom":"iphone",
29 | "size":"29x29",
30 | "scale":"3x",
31 | "filename":"Icon-App-29x29@3x.png"
32 | },
33 | {
34 | "idiom":"iphone",
35 | "size":"40x40",
36 | "scale":"1x",
37 | "filename":"Icon-App-40x40@1x.png"
38 | },
39 | {
40 | "idiom":"iphone",
41 | "size":"40x40",
42 | "scale":"2x",
43 | "filename":"Icon-App-40x40@2x.png"
44 | },
45 | {
46 | "idiom":"iphone",
47 | "size":"40x40",
48 | "scale":"3x",
49 | "filename":"Icon-App-40x40@3x.png"
50 | },
51 | {
52 | "idiom":"iphone",
53 | "size":"57x57",
54 | "scale":"1x",
55 | "filename":"Icon-App-57x57@1x.png"
56 | },
57 | {
58 | "idiom":"iphone",
59 | "size":"57x57",
60 | "scale":"2x",
61 | "filename":"Icon-App-57x57@2x.png"
62 | },
63 | {
64 | "idiom":"iphone",
65 | "size":"60x60",
66 | "scale":"1x",
67 | "filename":"Icon-App-60x60@1x.png"
68 | },
69 | {
70 | "idiom":"iphone",
71 | "size":"60x60",
72 | "scale":"2x",
73 | "filename":"Icon-App-60x60@2x.png"
74 | },
75 | {
76 | "idiom":"iphone",
77 | "size":"60x60",
78 | "scale":"3x",
79 | "filename":"Icon-App-60x60@3x.png"
80 | },
81 | {
82 | "idiom":"iphone",
83 | "size":"76x76",
84 | "scale":"1x",
85 | "filename":"Icon-App-76x76@1x.png"
86 | },
87 | {
88 | "idiom":"ipad",
89 | "size":"20x20",
90 | "scale":"1x",
91 | "filename":"Icon-App-20x20@1x.png"
92 | },
93 | {
94 | "idiom":"ipad",
95 | "size":"20x20",
96 | "scale":"2x",
97 | "filename":"Icon-App-20x20@2x.png"
98 | },
99 | {
100 | "idiom":"ipad",
101 | "size":"29x29",
102 | "scale":"1x",
103 | "filename":"Icon-App-29x29@1x.png"
104 | },
105 | {
106 | "idiom":"ipad",
107 | "size":"29x29",
108 | "scale":"2x",
109 | "filename":"Icon-App-29x29@2x.png"
110 | },
111 | {
112 | "idiom":"ipad",
113 | "size":"40x40",
114 | "scale":"1x",
115 | "filename":"Icon-App-40x40@1x.png"
116 | },
117 | {
118 | "idiom":"ipad",
119 | "size":"40x40",
120 | "scale":"2x",
121 | "filename":"Icon-App-40x40@2x.png"
122 | },
123 | {
124 | "size" : "50x50",
125 | "idiom" : "ipad",
126 | "filename" : "Icon-Small-50x50@1x.png",
127 | "scale" : "1x"
128 | },
129 | {
130 | "size" : "50x50",
131 | "idiom" : "ipad",
132 | "filename" : "Icon-Small-50x50@2x.png",
133 | "scale" : "2x"
134 | },
135 | {
136 | "idiom":"ipad",
137 | "size":"72x72",
138 | "scale":"1x",
139 | "filename":"Icon-App-72x72@1x.png"
140 | },
141 | {
142 | "idiom":"ipad",
143 | "size":"72x72",
144 | "scale":"2x",
145 | "filename":"Icon-App-72x72@2x.png"
146 | },
147 | {
148 | "idiom":"ipad",
149 | "size":"76x76",
150 | "scale":"1x",
151 | "filename":"Icon-App-76x76@1x.png"
152 | },
153 | {
154 | "idiom":"ipad",
155 | "size":"76x76",
156 | "scale":"2x",
157 | "filename":"Icon-App-76x76@2x.png"
158 | },
159 | {
160 | "idiom":"ipad",
161 | "size":"76x76",
162 | "scale":"3x",
163 | "filename":"Icon-App-76x76@3x.png"
164 | },
165 | {
166 | "idiom":"ipad",
167 | "size":"83.5x83.5",
168 | "scale":"2x",
169 | "filename":"Icon-App-83.5x83.5@2x.png"
170 | }
171 | ],
172 | "info":{
173 | "version":1,
174 | "author":"makeappicon"
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/demo/KSYAirStreamer/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYAirStreamer-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.5.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.5.0.0
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSAllowsArbitraryLoads
30 |
31 |
32 | NSCameraUsageDescription
33 |
34 | NSMicrophoneUsageDescription
35 |
36 | UIBackgroundModes
37 |
38 | audio
39 | voip
40 |
41 | UIFileSharingEnabled
42 |
43 | UILaunchStoryboardName
44 | LaunchScreen
45 | UIMainStoryboardFile
46 | Main
47 | UIRequiredDeviceCapabilities
48 |
49 | armv7
50 |
51 | UISupportedInterfaceOrientations
52 |
53 | UIInterfaceOrientationPortrait
54 | UIInterfaceOrientationLandscapeLeft
55 | UIInterfaceOrientationLandscapeRight
56 |
57 | UISupportedInterfaceOrientations~ipad
58 |
59 | UIInterfaceOrientationPortrait
60 | UIInterfaceOrientationPortraitUpsideDown
61 | UIInterfaceOrientationLandscapeLeft
62 | UIInterfaceOrientationLandscapeRight
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYAirStreamer-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header
3 | //
4 | // The contents of this file are implicitly included at the beginning of every source file.
5 | //
6 |
7 | #import
8 |
9 | #ifndef __IPHONE_5_0
10 | #warning "This project uses features only available in iOS SDK 5.0 and later."
11 | #endif
12 |
13 | #ifdef __OBJC__
14 | @import UIKit;
15 | @import Foundation;
16 | #endif
17 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYAirView.h:
--------------------------------------------------------------------------------
1 | //
2 | // KSYAirView.h
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 | #import "KSYUIView.h"
11 | #import "KSYAirStreamKit.h"
12 |
13 | @interface KSYAirView : KSYUIView
14 |
15 | // 推流地址
16 | @property UITextField * txtAddr;
17 | // 分辨率选择
18 | @property UISegmentedControl *resolutionUI;
19 | // 硬解/软解
20 | @property UISegmentedControl *videoDecoderUI;
21 | // 是否固定宽屏(锁定分辨率, 竖屏时填黑边)
22 | @property UISwitch *paddingUI;
23 | // 帧率选择
24 | @property UISegmentedControl *framerateUI;
25 | // 码率选择
26 | @property KSYNameSlider *videoBitrateUI;
27 | // 话筒音量
28 | @property KSYNameSlider *micVolumeUI;
29 | // 开始推流
30 | @property UIButton * btn;
31 | // 开始录制
32 | @property UIButton * btnRec;
33 |
34 | // 推流状态
35 | @property NSString * strState;
36 | // airplay 状态
37 | @property NSString * airState;
38 |
39 | // 从当前界面获取配置信息
40 | @property (nonatomic, readonly) KSYAirTunesConfig * airCfg;
41 | @end
42 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYAirView.m:
--------------------------------------------------------------------------------
1 | //
2 | // KSYAirView.m
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 | #import "KSYAirView.h"
9 |
10 | @interface KSYAirView (){
11 |
12 | }
13 | @property UIButton * doneBtn;
14 | // 推流状态控件
15 | @property UILabel * lblState;
16 | @property UILabel * lblRes;
17 | @property UILabel * lblPadding;
18 | @property UILabel * lblFPS;
19 |
20 | @end
21 |
22 | @implementation KSYAirView
23 |
24 | - (id) init {
25 | self = [super init];
26 | if (self == nil) {
27 | return nil;
28 | }
29 | // hostURL = rtmpSrv + streamName(随机数,避免多个demo推向同一个流
30 | NSString *rtmpSrv = @"rtmp://test.uplive.ks-cdn.com/live";
31 | NSString *devCode = [[KSYUIView getUuid] substringToIndex:3];
32 | NSString *url = [NSString stringWithFormat:@"%@/%@", rtmpSrv, devCode];
33 | _txtAddr = [self addTextField:url ];
34 | _doneBtn = [self addButton:@"ok"];
35 |
36 | _lblRes = [self addLable:@"分辨率"];
37 | _resolutionUI = [self addSegCtrlWithItems:@[@"低",@"中", @"高"]];
38 | _resolutionUI.selectedSegmentIndex = 2;
39 | _videoDecoderUI = [self addSegCtrlWithItems:@[@"软解码",@"硬解码"]];
40 | _videoDecoderUI.selectedSegmentIndex = 1;
41 | _btnRec = [self addButton:@"_ 无解码 "];
42 |
43 | CGSize sz = [[UIScreen mainScreen] bounds].size;
44 | int hgt = MAX(sz.width, sz.height);
45 | if (hgt <=568) {
46 | _videoDecoderUI.hidden = YES;
47 | _videoDecoderUI.selectedSegmentIndex = 0;
48 | }
49 | _lblFPS = [self addLable:@"帧率"];
50 | _framerateUI = [self addSegCtrlWithItems:@[@"10",@"15", @"30"]];
51 | _framerateUI.selectedSegmentIndex = 1;
52 | _lblPadding = [self addLable:@"固定宽屏"];
53 | _paddingUI = [self addSwitch:NO];
54 | _videoBitrateUI = [self addSliderName:@"码率" From:500 To:3000 Init:1400];
55 | _micVolumeUI = [self addSliderName:@"音量" From:0 To:2 Init:1];
56 |
57 | _btn = [self addButton:@"开始"];
58 | [_btn setTitle:@"停止" forState:UIControlStateSelected ];
59 | _airState = @"";
60 | _strState = @"";
61 | _lblState = [self addLable:@"idle"];
62 | _lblState.numberOfLines = 3;
63 | return self;
64 | }
65 | - (void) dealloc {
66 |
67 | }
68 | //UIControlEventTouchUpInside
69 | - (IBAction)onBtn:(id)sender{
70 | if (sender == _doneBtn){
71 | [_txtAddr resignFirstResponder];
72 | return;
73 | }
74 | [super onBtn:sender];
75 | }
76 | - (void) layoutUI {
77 | [super layoutUI];
78 | if ( self.width < self.height) {
79 | self.btnH = self.btnH*1.5;
80 | }
81 | [self putWide: _txtAddr andNarrow: _doneBtn];
82 | [self putLable:_lblRes andView:_resolutionUI ];
83 | [self putWide:_videoDecoderUI andNarrow:_btnRec];
84 | [self putRow:@[_lblFPS,_framerateUI, _lblPadding, _paddingUI ] ];
85 | [self putRow:@[_videoBitrateUI] ];
86 | [self putRow:@[_micVolumeUI] ];
87 | [self putRow:@[_btn] ];
88 |
89 | self.btnH = self.height - self.yPos - self.gap;
90 | [self putRow1:_lblState];
91 | }
92 | @synthesize strState = _strState;
93 | - (NSString *)strState {
94 | return _strState;
95 | }
96 | - (void)setStrState:(NSString *)strState {
97 | _strState = strState;
98 | _lblState.text = [NSString stringWithFormat:@"%@\n%@",_airState, _strState];
99 | }
100 | @synthesize airState = _airState;
101 | - (NSString *)airState {
102 | return _airState;
103 | }
104 | - (void)setAirState:(NSString *)airState {
105 | _airState = airState;
106 | _lblState.text = [NSString stringWithFormat:@"%@\n%@",_airState, _strState];
107 | }
108 |
109 | - (KSYAirTunesConfig *) airCfg {
110 | KSYAirTunesConfig *cfg = [[KSYAirTunesConfig alloc] init];
111 | cfg.framerate = [_framerateUI titleForSegmentAtIndex:_framerateUI.selectedSegmentIndex].intValue;
112 | NSString * name = [_txtAddr.text substringFromIndex:_txtAddr.text.length-3];
113 | cfg.airplayName = [NSString stringWithFormat:@"ksyair_%@", name];
114 | int targetWdt =[self getResolution];
115 | if(_paddingUI.isOn) {
116 | cfg.padding = YES;
117 | CGSize screenSz = [UIScreen mainScreen].bounds.size;
118 | CGFloat wdt = MAX(screenSz.width, screenSz.height);
119 | CGFloat hgt = MIN(screenSz.width, screenSz.height);
120 | CGFloat targetHgt =ceil(targetWdt*hgt/wdt);
121 | if (targetHgt< 720) { // wdt and hgt must larger than 720
122 | targetHgt = targetWdt;
123 | cfg.padding = NO;
124 | _paddingUI.on = NO;
125 | }
126 | cfg.videoSize = CGSizeMake(targetWdt, targetHgt);
127 | }
128 | else {
129 | cfg.videoSize = CGSizeMake(targetWdt, targetWdt);
130 | }
131 | if (_videoDecoderUI.selectedSegmentIndex == 0) {
132 | cfg.videoDecoder = KSYAirVideoDecoder_SOFTWARE;
133 | }
134 | else {
135 | cfg.videoDecoder = KSYAirVideoDecoder_VIDEOTOOLBOX;
136 | }
137 | return cfg;
138 | }
139 | - (int) getResolution {
140 | switch (_resolutionUI.selectedSegmentIndex) {
141 | case 0:
142 | return 720;
143 | case 1:
144 | return 960;
145 | case 2:
146 | return 1280;
147 | default:
148 | return 1280;
149 | }
150 | }
151 | - (IBAction)onSegCtrl:(id)sender {
152 | [super onSegCtrl:sender];
153 | if (sender == _resolutionUI ) {
154 | _paddingUI.enabled = NO;
155 | if (_resolutionUI.selectedSegmentIndex == 2) {
156 | _paddingUI.enabled = YES;
157 | }
158 | }
159 | }
160 | @end
161 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // KSYAppDelegate.h
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 |
11 | @interface KSYAppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // KSYAppDelegate.m
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 |
9 | #import "KSYAppDelegate.h"
10 | #import
11 |
12 | @implementation KSYAppDelegate
13 |
14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
15 | {
16 | BuglyConfig * cfg = [[BuglyConfig alloc] init];
17 | cfg.channel = @"internal";
18 | [Bugly startWithAppId:@"948b45ab60" config:cfg];
19 | return YES;
20 | }
21 |
22 | - (void)applicationWillResignActive:(UIApplication *)application
23 | {
24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
26 | }
27 |
28 | - (void)applicationDidEnterBackground:(UIApplication *)application
29 | {
30 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
32 | }
33 |
34 | - (void)applicationWillEnterForeground:(UIApplication *)application
35 | {
36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
37 | }
38 |
39 | - (void)applicationDidBecomeActive:(UIApplication *)application
40 | {
41 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
42 | }
43 |
44 | - (void)applicationWillTerminate:(UIApplication *)application
45 | {
46 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
47 | }
48 |
49 | @end
50 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYBitStreamDumpVC.h:
--------------------------------------------------------------------------------
1 | //
2 | // KSYViewController.h
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 | #import "KSYUIVC.h"
11 |
12 | @interface KSYBitStreamDumpVC : KSYUIVC
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYBitStreamDumpVC.m:
--------------------------------------------------------------------------------
1 | //
2 | // KSYViewController.m
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 | #import "KSYBitStreamDumpVC.h"
9 | #import
10 | #import "KSYUIView.h"
11 |
12 | @interface KSYBitStreamDumpVC () {
13 | KSYAirTunesServer * _airSer;
14 | }
15 |
16 | @property UIButton * btnStart;
17 | @property UIButton * btnQuit;
18 | @property UILabel * lblState;
19 |
20 | @property (nonatomic, readwrite, retain) NSString * airState;
21 |
22 | @end
23 |
24 | @implementation KSYBitStreamDumpVC
25 |
26 |
27 | - (void)viewDidLoad {
28 | [super viewDidLoad];
29 |
30 | self.layoutView = [[KSYUIView alloc] init];
31 | self.layoutView.frame = self.view.frame;
32 | self.layoutView.backgroundColor = [UIColor whiteColor];
33 | self.view = self.layoutView;
34 | _btnStart = [self.layoutView addButton:@"开始"];
35 | _btnQuit = [self.layoutView addButton:@"退出"];
36 | _lblState = [self.layoutView addLable:@"idle"];
37 | _lblState.lineBreakMode = NSLineBreakByWordWrapping;
38 |
39 | __weak typeof (self) selfWeak = self;
40 | self.layoutView.onBtnBlock = ^(id sender){
41 | [selfWeak onBtnPress:sender];
42 | };
43 |
44 | NSString* token = @"bx5s51alwro/JtVWuOmN0OmdyzwFVMtH9AL5SWoe7dmjETWIZYib876DeYUXzlgdeurwIJeXsvpSnWlj139GMOqF+OK2Lia2ixuxfOdIyi+mp7PVDwPN5O8H6vk5mITgn8NMI95tarS0pPgMnP+w5h9EAZL96bGR2QOCUK+4NSk=";
45 | NSError * err = nil;
46 | _airSer = [[KSYAirTunesServer alloc] initWithToken:token error:&err];
47 | if ( err ) {
48 | NSLog(@"auth failed: %@", err.localizedDescription);
49 | self.airState = err.localizedDescription;
50 | return;
51 | }
52 | _airSer.delegate = self;
53 | _airSer.videoBitStreamCallback = ^(NSData *data, BOOL bParameterSet, CMTime timeInfo) {
54 | if(bParameterSet) {
55 | ///软解的话, data.bytes 可以直接送入ffmpeg的extradata解码
56 | ///videotoolbox的话, 可用如下代码先解析
57 | size_t psCnt = 0;
58 | uint8_t * psData[8] ={NULL};
59 | size_t psLen[8] = {0};
60 | int nalLen = KSYAirParseParamSets(data, psData, psLen, &psCnt);
61 | NSLog(@"paramset %zu", psCnt);
62 | NSLog(@"nalLen %d", nalLen);
63 | for (int i = 0; i < psCnt; ++i) {
64 | if (psData[i]) {
65 | free(psData[i]);
66 | }
67 | }
68 | }
69 | else {
70 | int32_t* pNalLen = (int32_t*)data.bytes;
71 | int32_t nalLen = CFSwapInt32(*pNalLen);
72 | NSLog(@"get bit stream %ld %d", (long)data.length, nalLen+4);
73 | }
74 | };
75 | NSNotificationCenter* dc = [NSNotificationCenter defaultCenter];
76 | [dc addObserver:self
77 | selector:@selector(appEnterBackground)
78 | name:UIApplicationDidEnterBackgroundNotification
79 | object:nil];
80 | }
81 |
82 | - (void)didReceiveMemoryWarning {
83 | [super didReceiveMemoryWarning];
84 | // Dispose of any resources that can be recreated.
85 | }
86 | - (void)viewDidAppear:(BOOL)animated {
87 | [self layoutUI];
88 | }
89 | - (BOOL)shouldAutorotate {
90 | [self layoutUI];
91 | return YES;
92 | }
93 |
94 | - (void) layoutUI {
95 | [super layoutUI];
96 | self.layoutView.btnH *= 2;
97 | self.layoutView.yPos += self.layoutView.btnH;
98 | [self.layoutView putRow1:_btnStart];
99 | self.layoutView.btnH *= 2;
100 | [self.layoutView putRow1:_lblState];
101 | self.layoutView.btnH = _btnStart.frame.size.height;
102 | [self.layoutView putRow1:_btnQuit];
103 | }
104 |
105 | - (void) viewWillTransitionToSize:(CGSize)size
106 | withTransitionCoordinator:(id) coordinator{
107 | [coordinator animateAlongsideTransition:^(id context) {
108 | }completion:^(id context) {
109 | [self layoutUI];
110 | }];
111 | }
112 |
113 | - (IBAction)onBtnPress:(id)sender {
114 | if (sender == _btnStart) {
115 | _btnStart.selected = ! _btnStart.selected;
116 | if (_btnStart.selected) {
117 | KSYAirTunesConfig * cfg = [[KSYAirTunesConfig alloc] init];
118 | cfg.videoDecoder = KSYAirVideoDecoder_NONE;
119 | [_airSer startServerWithCfg:cfg];
120 | }
121 | else {
122 | [_airSer stopServer];
123 | }
124 | }
125 | else if (sender == _btnQuit) {
126 | [self dismissViewControllerAnimated:NO
127 | completion:nil];
128 | }
129 | }
130 |
131 | - (void) dealloc {
132 | [[NSNotificationCenter defaultCenter] removeObserver:self];
133 | }
134 |
135 | #pragma mark - KSYAirDelegate
136 | - (void) didStartMirroring:(KSYAirTunesServer *)server {
137 | self.airState = @"mirroring";
138 | }
139 | - (void)mirroringErrorDidOcccur:(KSYAirTunesServer *)server withError:(NSError *)error {
140 | _btnStart.selected = NO;
141 | self.airState = [NSString stringWithFormat:@"error: %@", error.localizedDescription];
142 | }
143 | - (void)didStopMirroring:(KSYAirTunesServer *)server {
144 | self.airState = @"idle";
145 | _btnStart.enabled = YES;
146 | }
147 | - (void) setAirState:(NSString *)airState {
148 | dispatch_async(dispatch_get_main_queue(), ^{
149 | _lblState.text = airState;
150 | });
151 | }
152 |
153 |
154 | /** 进入后台 */
155 | - (void) appEnterBackground {
156 | // 开启后台任务,避免被suspend
157 | __block UIBackgroundTaskIdentifier bgTask;
158 | dispatch_queue_t bgTaskQ = dispatch_queue_create("com.ksyun.backgroundTask.queue", DISPATCH_QUEUE_SERIAL);
159 | bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^ {
160 | [[UIApplication sharedApplication] endBackgroundTask:bgTask];
161 | bgTask = UIBackgroundTaskInvalid;
162 | }];
163 | dispatch_async(bgTaskQ, ^{
164 | int cnt = 0;
165 | while ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
166 | NSLog(@"runing %d", cnt++);
167 | sleep(1);
168 | }
169 | [[UIApplication sharedApplication] endBackgroundTask:bgTask];
170 | bgTask = UIBackgroundTaskInvalid;
171 | });
172 | }
173 |
174 |
175 | @end
176 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYUIUtils/KSYDrawingView.h:
--------------------------------------------------------------------------------
1 | //
2 | // KSYDrawingView.h
3 | // KSYGPUStreamerDemo
4 | //
5 | // Created by 江东 on 17/6/19.
6 | // Copyright © 2017年 ksyun. All rights reserved.
7 | //
8 |
9 | #import "KSYUIView.h"
10 | #import
11 |
12 | @interface KSYDrawingView: KSYUIView
13 |
14 | - (id)initDraw:(CGRect)rect;
15 |
16 | @end
17 |
18 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYUIUtils/KSYDrawingView.m:
--------------------------------------------------------------------------------
1 | //
2 | // KSYDrawingView.h
3 | // KSYGPUStreamerDemo
4 | //
5 | // Created by 江东 on 17/6/19.
6 | // Copyright © 2017年 ksyun. All rights reserved.
7 | //
8 |
9 | #import "KSYDrawingView.h"
10 | #import
11 |
12 | @interface KSYDrawingView ()
13 |
14 | @property (nonatomic, strong) UIBezierPath *path;
15 | @property CGRect cgRect;
16 |
17 | @end
18 | @implementation KSYDrawingView
19 |
20 | + (Class)layerClass{
21 | //this makes our view create a CAShapeLayer
22 | //instead of a CALayer for its backing layer
23 | return [CAShapeLayer class];
24 | }
25 |
26 | - (id)initDraw:(CGRect)rect{
27 | self = [super init];
28 | [self awakeFromNib];
29 | _cgRect = rect;
30 | return self;
31 | }
32 |
33 | - (void)awakeFromNib{
34 | [super awakeFromNib];
35 | //create a mutable path
36 | self.path = [[UIBezierPath alloc] init];
37 |
38 | //configure the layer
39 | CAShapeLayer *shapeLayer = (CAShapeLayer *)self.layer;
40 | shapeLayer.strokeColor = [UIColor redColor].CGColor;
41 | shapeLayer.fillColor = [UIColor clearColor].CGColor;
42 | shapeLayer.lineJoin = kCALineJoinRound;
43 | shapeLayer.lineCap = kCALineCapRound;
44 | shapeLayer.lineWidth = 5;
45 | }
46 |
47 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
48 | //get the starting point
49 | CGPoint point = [[touches anyObject] locationInView:self];
50 |
51 | //move the path drawing cursor to the starting point
52 | [self.path moveToPoint:point];
53 | }
54 |
55 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
56 | //get the current point
57 | CGPoint point = [[touches anyObject] locationInView:self];
58 | if ((point.y > 0) && (point.y < _cgRect.size.height - 1)){
59 | //add a new line segment to our path
60 | [self.path addLineToPoint:point];
61 | }
62 | //update the layer with a copy of the path
63 | ((CAShapeLayer *)self.layer).path = self.path.CGPath;
64 | }
65 | @end
66 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYUIUtils/KSYFileSelector.h:
--------------------------------------------------------------------------------
1 | //
2 | // KSYFileSelector.h
3 | // KSYGPUStreamerDemo
4 | //
5 | // Created by pengbin on 7/23/16.
6 | // Copyright © 2016 ksyun. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | typedef NS_ENUM(NSInteger, KSYSelectType){
12 | KSYSelectType_NEXT = 0,
13 | KSYSelectType_RANDOM,
14 | KSYSelectType_PREVIOUS
15 | };
16 | /** 文件选择工具
17 | 在app的目录下选择特定文件用的工具类
18 |
19 | 指定app的NSHomeDirectory下面的"yyy/xxx"为目标目录
20 | 指定需要选择的文件后缀列表
21 | 初始化时,得到将Documents/xxx目录下的所有后缀匹配的文件列表
22 | 调用下个时,返回下一个文件的路径
23 | 维护一个状态信息字符串,格式如下
24 | "文件名(index/满足条件的文件总数)"
25 |
26 | */
27 | @interface KSYFileSelector : NSObject
28 |
29 | #pragma mark - configs
30 | //目标目录, 比如"Documents/bgms"
31 | @property (atomic, copy) NSString* filesDir;
32 |
33 | // 需要匹配的文件后缀列表
34 | @property (atomic, copy) NSArray* suffixList;
35 |
36 | #pragma mark - results
37 | // 满足条件的文件列表
38 | @property (atomic, readonly) NSArray* fileList;
39 |
40 | // 当前选中的文件的路径(完整路径)
41 | @property (atomic, readonly) NSString* filePath;
42 |
43 | // 当前正在播放的音乐文件的索引
44 | @property (atomic, readonly) NSInteger fileIdx;
45 |
46 | // "文件名(index/满足条件的文件总数)"
47 | @property (atomic, readonly) NSString* fileInfo;
48 |
49 | #pragma mark - methods
50 |
51 | /**
52 | @abstract 初始化
53 | @param dir 目标地址
54 | @param suf 后缀列表, 比如: @[".mp3", ".aac"]
55 | */
56 | - (id) initWithDir: (NSString *) dir
57 | andSuffix: (NSArray *) suf;
58 |
59 | /**
60 | @abstract 重新载入
61 | @discussion 当目标目录和后缀列表有变动, 或者目录中文件有增减时可用与重新生成列表
62 | @discussion reload后, fileIdx 重置为0
63 | @return NO: 1. 目标目录不存在, 2. 目标目录中没有满足条件的文件
64 | */
65 | - (BOOL) reload;
66 |
67 | /**
68 | @abstract 获取一个文件
69 | */
70 | - (BOOL) selectFileWithType:(KSYSelectType)type;
71 | @end
72 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYUIUtils/KSYFileSelector.m:
--------------------------------------------------------------------------------
1 | //
2 | // KSYFileSelector.m
3 | // KSYGPUStreamerDemo
4 | //
5 | // Created by pengbin on 7/23/16.
6 | // Copyright © 2016 ksyun. All rights reserved.
7 | //
8 |
9 | #import "KSYFileSelector.h"
10 | @interface KSYFileSelector(){
11 | NSString* _fullDir;
12 | }
13 |
14 | @end
15 |
16 | @implementation KSYFileSelector
17 |
18 |
19 | /**
20 | @abstract 初始化
21 | @param dir 目标地址
22 | @param suf 后缀列表, 比如: @[".mp3", ".aac"]
23 | */
24 | - (id) initWithDir: (NSString *) dir
25 | andSuffix: (NSArray *) suf{
26 | if (( dir == nil) ||
27 | ([dir length] == 0)||
28 | ([suf count] == 0)){
29 | return nil;
30 | }
31 | self =[super init];
32 | _filesDir = dir;
33 | _suffixList = suf;
34 | [self reload];
35 | return self;
36 | }
37 |
38 | /**
39 | @abstract 重新载入
40 | @discussion 当目标目录和后缀列表有变动, 或者目录中文件有增减时可用与重新生成列表
41 | @discussion reload后, fileIdx 重置为0
42 | @return NO: 1. 目标目录不存在, 2. 目标目录中没有满足条件的文件
43 | */
44 | - (BOOL) reload{
45 | _fullDir = [NSHomeDirectory() stringByAppendingString:_filesDir];
46 |
47 | NSFileManager * fmgr = [NSFileManager defaultManager];
48 | NSArray * list = [fmgr contentsOfDirectoryAtPath:_fullDir
49 | error:nil];
50 | _fileList = @[];
51 | // filter all files
52 | for (NSString* f in list) {
53 | for (NSString* p in _suffixList) {
54 | if ( [f hasSuffix:p]){
55 | _fileList = [_fileList arrayByAddingObject:f];
56 | break;
57 | }
58 | }
59 | }
60 | _fileIdx = -1;
61 | NSLog(@"find %lu in %@", (unsigned long)[_fileList count], _filesDir);
62 | return [self selectFileWithType:KSYSelectType_NEXT];
63 | }
64 |
65 | /**
66 | @abstract 获取一个音频文件
67 | */
68 | - (BOOL)selectFileWithType:(KSYSelectType)type{
69 | NSInteger cnt =[_fileList count];
70 | if (cnt == 0) { // no file
71 | _fileInfo = @"can't find any file";
72 | _filePath = nil;
73 | return NO;
74 | }
75 | if (type == KSYSelectType_NEXT) {
76 | //next
77 | _fileIdx = (_fileIdx+1)%cnt;
78 | }
79 | else if (type == KSYSelectType_RANDOM){
80 | //random
81 | _fileIdx = (NSInteger)(arc4random() % cnt);
82 | }
83 | else if (type == KSYSelectType_PREVIOUS){
84 | //previous
85 | _fileIdx = (_fileIdx+cnt-1)%cnt;
86 | }
87 | else {
88 | return NO;
89 | }
90 | NSString * name = _fileList[_fileIdx];
91 | _fileInfo = [NSString stringWithFormat:@" %@(%ld/%d)",name,(long)_fileIdx,(int)cnt];
92 | _filePath = [_fullDir stringByAppendingString:name];
93 | return YES;
94 | }
95 | @end
96 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYUIUtils/KSYNameSlider.h:
--------------------------------------------------------------------------------
1 |
2 | #import
3 |
4 |
5 | /**
6 | 自定义控件: 带标签的slider
7 |
8 | 控件格式为: 参数名称 + 滑块 + 当前数值
9 | 通过 onSliderBlock,可以得到滑块值改变的事件
10 | 通过 normalValue, 可以查询到归一化的值(0~1.0)
11 | */
12 | @interface KSYNameSlider: UIView
13 |
14 | @property (atomic, retain) UILabel* nameL; // 滑块对应参数名称
15 | @property (atomic, retain) UISlider* slider; // 滑块
16 | @property (atomic, retain) UILabel* valueL; // 滑块当前的值
17 | // UIControlEventValueChanged 回调
18 | @property(nonatomic, copy) void(^onSliderBlock)(id sender);
19 |
20 | // normalize between 0.0~1.0 [ (value-min)/max ]
21 | @property (atomic, assign) float normalValue;
22 |
23 | // slider value
24 | @property (atomic, assign) float value;
25 |
26 |
27 | // 值的小数点位数[0,1,2,3]
28 | @property (atomic, assign) int precision;
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYUIUtils/KSYNameSlider.m:
--------------------------------------------------------------------------------
1 |
2 | #import "KSYNameSlider.h"
3 |
4 | @implementation KSYNameSlider
5 |
6 | - (id) init {
7 | self = [super init];
8 | UIColor* bgColor = [UIColor colorWithWhite:0.8 alpha:0.3];
9 | self.nameL = [[UILabel alloc] init];
10 | self.valueL = [[UILabel alloc] init];
11 | self.nameL.backgroundColor = bgColor;
12 | self.valueL.backgroundColor = bgColor;
13 | self.slider = [[UISlider alloc] init];
14 | self.slider.minimumValue = 0;
15 | self.slider.maximumValue = 100;
16 | [self addSubview:_nameL];
17 | [self addSubview:_valueL];
18 | [self addSubview:_slider];
19 | [_slider addTarget:self
20 | action:@selector(onSlider:)
21 | forControlEvents:UIControlEventValueChanged];
22 | self.valueL.textAlignment = NSTextAlignmentCenter;
23 | self.onSliderBlock = nil;
24 | _normalValue = (_slider.value - _slider.minimumValue) / _slider.maximumValue;
25 | _precision = 0;
26 | return self;
27 | }
28 | - (void)setFrame:(CGRect)frame{
29 | [super setFrame:frame];
30 | [self layoutSlider];
31 | }
32 | - (void)layoutSlider{
33 | CGFloat wdt = self.frame.size.width;
34 | CGFloat hgt = self.frame.size.height;
35 | [_nameL sizeToFit];
36 | [_valueL sizeToFit];
37 |
38 | CGFloat wdtN = _nameL.frame.size.width + 10;
39 | CGFloat wdtV = _valueL.frame.size.width + 10;
40 | CGFloat wdtS = wdt - wdtN - wdtV;
41 | _nameL.frame = CGRectMake(0, 0, wdtN, hgt);
42 | _slider.frame = CGRectMake(wdtN, 0, wdtS, hgt);
43 | _valueL.frame = CGRectMake(wdtN+wdtS, 0,wdtV, hgt);
44 | }
45 | - (void) updateValue {
46 | float val = _slider.value;
47 | if (_precision == 0){
48 | _valueL.text = [NSString stringWithFormat:@"%d", (int)val];
49 | }
50 | else {
51 | NSString *fmt =[NSString stringWithFormat:@"%%0.%df", _precision];
52 | _valueL.text = [NSString stringWithFormat:fmt, val];
53 | }
54 | [self layoutSlider];
55 | _normalValue = (_slider.value - _slider.minimumValue) / _slider.maximumValue;
56 | }
57 |
58 | //UIControlEventValueChanged
59 | - (IBAction)onSlider:(id)sender {
60 | [self updateValue];
61 | if (_onSliderBlock) {
62 | _onSliderBlock(self);
63 | }
64 | }
65 |
66 | @synthesize normalValue = _normalValue;
67 | - (float)normalValue{
68 | return _normalValue;
69 | }
70 | - (void)setNormalValue:(float )val{
71 | _slider.value = val * _slider.maximumValue + _slider.minimumValue;
72 | [self updateValue];
73 | }
74 | - (float)value{
75 | return _slider.value;
76 | }
77 | - (void)setValue:(float )val{
78 | _slider.value = val;
79 | [self updateValue];
80 | if (_onSliderBlock) {
81 | _onSliderBlock(self);
82 | }
83 | }
84 | @end
85 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYUIUtils/KSYUIVC.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // KSYStreamerVC
4 | //
5 | // Created by yiqian on 10/15/15.
6 | // Copyright (c) 2015 qyvideo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | /**
12 | ksy自定义视图控制器
13 |
14 | 主要增加一些工具函数
15 | */
16 | #define SYSTEM_VERSION_GE_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
17 |
18 | @class KSYUIView;
19 | @interface KSYUIVC : UIViewController
20 |
21 | // 定时更新调试信息 // 每秒重复调用
22 | - (void)onTimer:(NSTimer *)theTimer ;
23 |
24 | // 在addObservers 中会注册此timer, 没秒重复调用onTimer
25 | @property NSTimer *timer;
26 |
27 | // 在viewDidLoad 时会调用, 继承后重载该函数, 可以增加其他消息注册
28 | - (void) addObservers;
29 |
30 | // 如果没有主动调用, 会在 dealloc时调用
31 | - (void) rmObservers;
32 |
33 | // 重写本函数进行UI重新布局
34 | - (void) layoutUI;
35 |
36 | // 重写该方法来响应屏幕旋转
37 | - (void) onViewRotate;
38 |
39 | // 默认的控制视图
40 | @property KSYUIView *layoutView;
41 |
42 | // ksy util functions
43 | + (NSString*) sizeFormatted : (int )KB;
44 | + (NSString *)timeFormatted:(int)totalSeconds;
45 | + (void) toast:(NSString*)message
46 | time:(double)duration;
47 | //cpu use rate
48 | + (float) cpu_usage;
49 | + (float) memory_usage;
50 | + (int)getCurrentBatteryLevel;
51 |
52 | // 网络状态
53 | @property NSString* networkStatus;
54 | @property(nonatomic, copy) void(^onNetworkChange)(NSString* msg);
55 |
56 | // 将UIImage 保存到path对应的文件
57 | + (void)saveImage: (UIImage *)image
58 | to: (NSString*)path;
59 | + (void)saveImageToPhotosAlbum:(UIImage *)image;
60 | @end
61 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYUIUtils/KSYUIVC.m:
--------------------------------------------------------------------------------
1 | //
2 | // KSYUIVC.m
3 | // KSYStreamerVC
4 | //
5 | // Created by yiqian on 10/15/15.
6 | // Copyright (c) 2015 ksyun. All rights reserved.
7 | //
8 |
9 | #import "KSYUIVC.h"
10 | #import
11 | #import
12 | #import "KSYUIView.h"
13 |
14 | @interface KSYUIVC() {
15 | KSYReachability *_reach;
16 | KSYNetworkStatus _preStatue;
17 | }
18 |
19 | @end
20 |
21 | @implementation KSYUIVC
22 |
23 | #pragma mark - UIViewController
24 | - (void)viewDidLoad {
25 | [super viewDidLoad];
26 | [self addObservers];
27 | _networkStatus = @" ";
28 | }
29 |
30 | - (void) addObservers {
31 | // statistics update every seconds
32 | _timer = [NSTimer scheduledTimerWithTimeInterval:1.0
33 | target:self
34 | selector:@selector(onTimer:)
35 | userInfo:nil
36 | repeats:YES];
37 | NSNotificationCenter * dc = [NSNotificationCenter defaultCenter];
38 | [dc addObserver:self
39 | selector:@selector(netWorkChange)
40 | name:kKSYReachabilityChangedNotification
41 | object:nil];
42 | _reach = [KSYReachability reachabilityWithHostName:@"http://www.kingsoft.com"];
43 | [_reach startNotifier];
44 | }
45 |
46 | - (void)netWorkChange{
47 | KSYNetworkStatus currentStatus = [_reach currentReachabilityStatus];
48 | if (currentStatus == _preStatue) {
49 | return;
50 | }
51 | _preStatue = currentStatus;
52 | switch (currentStatus) {
53 | case KSYNotReachable:
54 | _networkStatus = @"无网络";
55 | break;
56 | case KSYReachableViaWWAN:
57 | _networkStatus = @"移动网络";
58 | break;
59 | case KSYReachableViaWiFi:
60 | _networkStatus = @"WIFI";
61 | break;
62 | default:
63 | return;
64 | }
65 | if( _onNetworkChange ){
66 | _onNetworkChange(_networkStatus);
67 | }
68 | }
69 | - (void) rmObservers {
70 | if (_timer) {
71 | [_timer invalidate];
72 | _timer = nil;
73 | }
74 | }
75 |
76 | - (void)viewWillAppear:(BOOL)animated {
77 | [super viewWillAppear:animated];
78 | [self layoutUI];
79 | }
80 |
81 | - (void)viewDidAppear:(BOOL)animated {
82 | [super viewDidAppear:animated];
83 | [UIApplication sharedApplication].idleTimerDisabled=YES;
84 | }
85 |
86 | - (void)viewWillDisappear:(BOOL)animated {
87 | [super viewWillDisappear: animated];
88 | [UIApplication sharedApplication].idleTimerDisabled=NO;
89 | }
90 |
91 | - (BOOL)shouldAutorotate {
92 | [self layoutUI];
93 | return [super shouldAutorotate];
94 | }
95 | - (void) dealloc {
96 | NSLog(@"dealloc");
97 | if (_timer) {
98 | [self rmObservers];
99 | }
100 | _reach = nil;
101 | [[NSNotificationCenter defaultCenter] removeObserver:self];
102 | }
103 |
104 | - (void) layoutUI {
105 | if(_layoutView){
106 | _layoutView.frame = self.view.frame;
107 | [_layoutView layoutUI];
108 | }
109 | }
110 |
111 | - (void)onTimer:(NSTimer *)theTimer{
112 |
113 | }
114 |
115 | #pragma mark - ui rotate
116 | - (void)viewWillTransitionToSize:(CGSize)size
117 | withTransitionCoordinator:(id)coordinator {
118 | [coordinator animateAlongsideTransition:^(id context) {
119 | if(SYSTEM_VERSION_GE_TO(@"8.0")) {
120 | [self onViewRotate];
121 | }
122 | }completion:^(id context) {
123 |
124 | }];
125 | [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
126 | }
127 |
128 | - (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
129 | if(SYSTEM_VERSION_GE_TO(@"8.0")) {
130 | return;
131 | }
132 | [self onViewRotate];
133 | }
134 | - (void) onViewRotate {
135 | // 子类 重写该方法来响应屏幕旋转
136 | }
137 |
138 | #pragma mark - string format
139 | + (NSString*) sizeFormatted : (int )KB {
140 | if ( KB > 1000 ) {
141 | double MB = KB / 1000.0;
142 | return [NSString stringWithFormat:@" %4.2f MB", MB];
143 | }
144 | else {
145 | return [NSString stringWithFormat:@" %d KB", KB];
146 | }
147 | }
148 |
149 | + (NSString *)timeFormatted:(int)totalSeconds
150 | {
151 | int seconds = totalSeconds % 60;
152 | int minutes = (totalSeconds / 60) % 60;
153 | int hours = totalSeconds / 3600;
154 | return [NSString stringWithFormat:@"%02d:%02d:%02d",hours, minutes, seconds];
155 | }
156 |
157 | + (void) toast:(NSString*)message
158 | time:(double)duration
159 | {
160 | UIAlertView *toast = [[UIAlertView alloc] initWithTitle:nil
161 | message:message
162 | delegate:nil
163 | cancelButtonTitle:@"OK"
164 | otherButtonTitles:nil, nil];
165 | [toast show];
166 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
167 | [toast dismissWithClickedButtonIndex:0 animated:YES];
168 | });
169 | }
170 |
171 | + (float) cpu_usage {
172 | kern_return_t kr;
173 | task_info_data_t tinfo;
174 | mach_msg_type_number_t task_info_count;
175 |
176 | task_info_count = TASK_INFO_MAX;
177 | kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count);
178 | if (kr != KERN_SUCCESS) {
179 | return -1;
180 | }
181 |
182 | task_basic_info_t basic_info;
183 | thread_array_t thread_list;
184 | mach_msg_type_number_t thread_count;
185 |
186 | thread_info_data_t thinfo;
187 | mach_msg_type_number_t thread_info_count;
188 |
189 | thread_basic_info_t basic_info_th;
190 | uint32_t stat_thread = 0; // Mach threads
191 |
192 | basic_info = (task_basic_info_t)tinfo;
193 |
194 | // get threads in the task
195 | kr = task_threads(mach_task_self(), &thread_list, &thread_count);
196 | if (kr != KERN_SUCCESS) {
197 | return -1;
198 | }
199 | if (thread_count > 0)
200 | stat_thread += thread_count;
201 |
202 | long tot_sec = 0;
203 | long tot_usec = 0;
204 | float tot_cpu = 0;
205 | int j;
206 |
207 | for (j = 0; j < thread_count; j++)
208 | {
209 | thread_info_count = THREAD_INFO_MAX;
210 | kr = thread_info(thread_list[j], THREAD_BASIC_INFO,
211 | (thread_info_t)thinfo, &thread_info_count);
212 | if (kr != KERN_SUCCESS) {
213 | return -1;
214 | }
215 |
216 | basic_info_th = (thread_basic_info_t)thinfo;
217 |
218 | if (!(basic_info_th->flags & TH_FLAGS_IDLE)) {
219 | tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds;
220 | tot_usec = tot_usec + basic_info_th->system_time.microseconds + basic_info_th->system_time.microseconds;
221 | tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0;
222 | }
223 |
224 | } // for each thread
225 |
226 | kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t));
227 | assert(kr == KERN_SUCCESS);
228 |
229 | return tot_cpu;
230 | }
231 |
232 | + (float)memory_usage {
233 | task_basic_info_data_t taskInfo;
234 | mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
235 | kern_return_t kernReturn = task_info(mach_task_self(),
236 | TASK_BASIC_INFO,
237 | (task_info_t)&taskInfo,
238 | &infoCount);
239 | if(kernReturn != KERN_SUCCESS) {
240 | return 0.0;
241 | }
242 | return taskInfo.resident_size / 1024.0 / 1024.0;
243 | }
244 |
245 | + (int)getCurrentBatteryLevel{
246 | //拿到当前设备
247 | UIDevice * device = [UIDevice currentDevice];
248 | //是否允许监测电池
249 | //要想获取电池电量信息和监控电池电量 必须允许
250 | device.batteryMonitoringEnabled = true;
251 | float level = device.batteryLevel;
252 | //换算为百分比
253 | int result = level * 100;
254 | return result;
255 | }
256 |
257 | #pragma mark - save Image
258 | // 将UIImage 保存到path对应的文件
259 | + (void)saveImage: (UIImage *)image
260 | to: (NSString*)path {
261 | NSString * dir = [NSHomeDirectory() stringByAppendingString:@"/Documents/"];
262 | NSString * file = [dir stringByAppendingPathComponent:path];
263 | NSData *imageData = UIImagePNGRepresentation(image);
264 | BOOL ret = [imageData writeToFile:file atomically:YES];
265 | NSLog(@"write %@ %@", file, ret ? @"OK":@"failed");
266 | }
267 |
268 | + (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
269 |
270 | if (error == nil) {
271 | UIAlertView *toast = [[UIAlertView alloc] initWithTitle:@"O(∩_∩)O~~"
272 | message:@"图像已保存至手机相册"
273 | delegate:nil
274 | cancelButtonTitle:@"确定"
275 | otherButtonTitles:nil, nil];
276 | [toast show];
277 |
278 | }else{
279 |
280 | UIAlertView *toast = [[UIAlertView alloc] initWithTitle:@" ̄へ ̄"
281 | message:@"图像保存手机相册失败!"
282 | delegate:nil
283 | cancelButtonTitle:@"确定"
284 | otherButtonTitles:nil, nil];
285 | [toast show];
286 | }
287 | }
288 |
289 | + (void)saveImageToPhotosAlbum:(UIImage *)image
290 | {
291 | UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
292 | }
293 | @end
294 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYUIUtils/KSYUIView.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Frames.h
3 | // KSYGPUStreamerDemo
4 | //
5 | // Created by 孙健 on 16/6/21.
6 | // Copyright © 2016年 ksyun. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "KSYNameSlider.h"
11 |
12 | /**
13 | KSY自定义视图
14 |
15 | 主要增加的功能如下:
16 | 1. 增加更方便的视图尺寸查询属性
17 | 2. 增加新建本SDK中常用控件的方法(按钮 滑块 开关等)
18 | 3. 绑定事件响应的回调
19 | 4. 增加简单的布局函数, 逐行添加任意数量的控件, 等大小,等距离放置
20 |
21 | 演示SDK接口的各种视图都继承自此类
22 | */
23 |
24 | @interface KSYUIView: UIView
25 | #pragma mark - init
26 | - (id) initWithParent:(KSYUIView*)pView;
27 |
28 | #pragma mark - geometry
29 | @property (nonatomic, assign) CGFloat x;
30 | @property (nonatomic, assign) CGFloat y;
31 | @property (nonatomic, assign) CGFloat width;
32 | @property (nonatomic, assign) CGFloat height;
33 | @property (nonatomic, assign) CGPoint origin;
34 | @property (nonatomic, assign) CGSize size;
35 |
36 | @property (nonatomic, assign) CGFloat gap; // gap between btns (default 4)
37 | @property (nonatomic, assign) CGFloat btnH; // button's height (default 40)
38 | // set by call [super layoutUI]
39 | @property (nonatomic, assign) CGFloat winWdt; // default self.width - gap*2
40 | @property (nonatomic, assign) CGFloat yPos; // default gap*5
41 | // 在布局函数中, 每次使用putXXX接口增加一行控件, yPos 往下增加btnH+gap
42 |
43 | #pragma mark - UI elements layout
44 | - (void) layoutUI;
45 | // 均匀放置一行控件 (每次调用将控件追加到上一行控件之下,行内均匀排布)
46 | - (void) putRow1:(UIView*)subV ;
47 | - (void) putRow2:(UIView*)subV0
48 | and:(UIView*)subV1;
49 | - (void) putRow3:(UIView*)subV0
50 | and:(UIView*)subV1
51 | and:(UIView*)subV2;
52 | // 均匀添加任意数量的视图数组
53 | - (void) putRow:(NSArray *) subV;
54 | // 添加任意数量的视图数组, 按照每个视图的内容调整宽度
55 | - (void) putRowFit:(NSArray *) subV;
56 |
57 | //(firstV 使用内容宽度, 剩余宽度全部分配给secondV)
58 | - (void) putNarrow:(UIView*)firstV
59 | andWide:(UIView*)secondV;
60 | //(secondV 使用内容宽度, 剩余宽度全部分配给firstV)
61 | - (void) putWide:(UIView*)firstV
62 | andNarrow:(UIView*)secondV;
63 |
64 | // 不均匀的放置一行中lable + subview
65 | - (void) putLable:(UIView*)lbl
66 | andView:(UIView*)subV;
67 | // 不均匀的将slider和switch放一行
68 | - (void) putSlider:(UIView*)sl
69 | andSwitch:(UIView*)sw;
70 |
71 | #pragma mark - new and add UI elements
72 | - (UITextField *) addTextField: (NSString*)text;
73 | - (UISegmentedControl *)addSegCtrlWithItems: (NSArray *) items;
74 | - (UILabel *)addLable: (NSString*)title;
75 | - (UIButton *)addButton:(NSString*)title;
76 | - (UISwitch *)addSwitch:(BOOL) on;
77 | - (UISlider *)addSliderFrom: (float) minV
78 | To: (float) maxV
79 | Init: (float) iniV;
80 |
81 | // 添加滑块 (结构为 名字+滑块+值)
82 | - (KSYNameSlider *)addSliderName: (NSString*) name
83 | From: (float) minV
84 | To: (float) maxV
85 | Init: (float) iniV;
86 |
87 | // button with custom action
88 | - (UIButton *)addButton:(NSString*)title
89 | action:(SEL)action;
90 | - (UISlider *)addSliderFrom: (float) minV
91 | To: (float) maxV
92 | Init: (float) iniV
93 | action: (SEL)action;
94 |
95 | #pragma mark - UI respond
96 | //UIControlEventTouchUpInside
97 | - (IBAction)onBtn:(id)sender;
98 | //UIControlEventValueChanged
99 | - (IBAction)onSwitch:(id)sender;
100 | //UIControlEventValueChanged
101 | - (IBAction)onSlider:(id)sender;
102 | //UIControlEventValueChanged
103 | - (IBAction)onSegCtrl:(id)sender;
104 |
105 | @property(nonatomic, copy) void(^onBtnBlock)(id sender);
106 | @property(nonatomic, copy) void(^onSwitchBlock)(id sender);
107 | @property(nonatomic, copy) void(^onSliderBlock)(id sender);
108 | @property(nonatomic, copy) void(^onSegCtrlBlock)(id sender);
109 |
110 | // 获取设备的UUID
111 | + (NSString *) getUuid;
112 | @end
113 |
114 | #define WeakObj(o) try{}@finally{} __weak typeof(o) o##Weak = o;
115 | #define weakObj(o) __weak typeof(o) o##Weak = o;
116 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYUIUtils/KSYUIView.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Frames.m
3 | // KSYGPUStreamerDemo
4 | //
5 | // Created by 孙健 on 16/6/21.
6 | // Copyright © 2016年 ksyun. All rights reserved.
7 | //
8 |
9 | #import "KSYUIView.h"
10 |
11 | @implementation KSYUIView
12 |
13 | - (CGFloat)x{
14 | return self.frame.origin.x;
15 | }
16 | - (void)setX:(CGFloat)x{
17 | CGRect frame = self.frame;
18 | frame.origin.x = x;
19 | self.frame = frame;
20 | }
21 |
22 | - (CGFloat)y{
23 | return self.frame.origin.y;
24 | }
25 | - (void)setY:(CGFloat)y{
26 | CGRect frame = self.frame;
27 | frame.origin.y = y;
28 | self.frame = frame;
29 | }
30 |
31 | - (CGFloat)width{
32 | return self.size.width;
33 | }
34 | - (void)setWidth:(CGFloat)width{
35 | CGRect frame = self.frame;
36 | frame.size.width = width;
37 | self.frame = frame;
38 | }
39 |
40 | - (CGFloat)height{
41 | return self.size.height;
42 | }
43 | - (void)setHeight:(CGFloat)height{
44 | CGRect frame = self.frame;
45 | frame.size.height = height;
46 | self.frame = frame;
47 | }
48 | - (CGPoint)origin{
49 | return self.origin;
50 | }
51 | - (void)setOrigin:(CGPoint)origin{
52 | CGRect frame = self.frame;
53 | frame.origin = origin;
54 | self.frame = frame;
55 | }
56 |
57 | - (CGSize)size{
58 | return self.frame.size;
59 | }
60 | - (void)setSize:(CGSize)size{
61 | CGRect frame = self.frame;
62 | frame.size = size;
63 | self.frame = frame;
64 | }
65 | #pragma mark - init
66 | - (id) initWithParent:(KSYUIView*)pView{
67 | self = [self init];
68 | if (pView){
69 | self.hidden = YES;
70 | [pView addSubview:self];
71 | }
72 | return self;
73 | }
74 | - (id) init {
75 | self = [super init];
76 | self.backgroundColor = [UIColor clearColor];
77 | self.gap = 4;
78 | self.btnH = 35;
79 | self.winWdt = self.width;
80 | return self;
81 | }
82 |
83 | #pragma mark - UI elements layout
84 | // 每次布局前,设置默认值
85 | - (void) layoutUI {
86 | self.btnH = 30;
87 | self.winWdt = self.width;
88 | self.yPos = [[UIApplication sharedApplication] statusBarFrame].size.height;
89 | }
90 | - (CGFloat) getXStart {
91 | CGFloat xPos = _gap;
92 | if (_yPos > self.height){
93 | xPos += _winWdt;
94 | }
95 | return xPos;
96 | }
97 |
98 | - (void) putRow:(NSArray *) subV {
99 | NSInteger cnt = [subV count];
100 | if ( cnt < 1){
101 | return ;
102 | }
103 | CGFloat btnW = (_winWdt/cnt) - _gap*2;
104 | CGFloat xPos = [self getXStart];
105 | CGFloat step = _gap*2+btnW;
106 | CGFloat yPos = _yPos > self.height ? _yPos - self.height : _yPos;
107 | for (id item in subV) {
108 | if ([item isKindOfClass:[UIView class]]){
109 | UIView * v = item;
110 | v.frame = CGRectMake(xPos, yPos, btnW, _btnH);
111 | }
112 | xPos += step;
113 | }
114 | _yPos += (_btnH + _gap);
115 | }
116 | - (void) putRowFit:(NSArray *) subV {
117 | NSInteger cnt = [subV count];
118 | if ( cnt < 1){
119 | return ;
120 | }
121 | CGFloat xPos = [self getXStart];
122 | CGFloat step = 0;
123 | CGFloat yPos = _yPos > self.height ? _yPos - self.height : _yPos;
124 | for (id item in subV) {
125 | if ([item isKindOfClass:[UIView class]]){
126 | UIView * v = item;
127 | [v sizeToFit];
128 | step = v.frame.size.width;
129 | v.frame = CGRectMake(xPos, yPos, step, _btnH);
130 | xPos += step;
131 | }
132 | else {
133 | xPos += 5;
134 | }
135 | }
136 | _yPos += (_btnH + _gap);
137 | }
138 |
139 | - (void) putRow1:(UIView*)subV {
140 | CGFloat yPos = _yPos > self.height ? _yPos - self.height : _yPos;
141 | subV.frame = CGRectMake([self getXStart], yPos, _winWdt - _gap*2, _btnH);
142 | _yPos += (_btnH + _gap);
143 | }
144 |
145 | - (void) putRow2:(UIView*)subV0
146 | and:(UIView*)subV1{
147 | CGFloat btnW = (_winWdt/2) - _gap*2;
148 | CGFloat y = _yPos > self.height ? _yPos - self.height : _yPos;
149 | CGFloat x = [self getXStart];
150 | subV0.frame = CGRectMake(x, y, btnW, _btnH);
151 | subV1.frame = CGRectMake(x+_gap*2+btnW, y, btnW, _btnH);
152 | _yPos += (_btnH + _gap);
153 | }
154 |
155 | - (void) putRow3:(UIView*)subV0
156 | and:(UIView*)subV1
157 | and:(UIView*)subV2 {
158 | CGFloat btnW = (_winWdt/3) - _gap*2;
159 |
160 | CGFloat x = [self getXStart];
161 | CGFloat y = _yPos > self.height ? _yPos - self.height : _yPos;
162 | CGFloat xPos[3] = {x, x+_gap*2+btnW, x+_gap*4+btnW*2};
163 | if (subV0){
164 | subV0.frame = CGRectMake(xPos[0], y, btnW, _btnH);
165 | }
166 | if (subV1) {
167 | subV1.frame = CGRectMake(xPos[1], y, btnW, _btnH);
168 | }
169 | if (subV2) {
170 | subV2.frame = CGRectMake(xPos[2], y, btnW, _btnH);
171 | }
172 | _yPos += (_btnH + _gap);
173 | }
174 |
175 |
176 | //(firstV 使用内容宽度, 剩余宽度全部分配给secondV)
177 | - (void) putNarrow:(UIView*)firstV
178 | andWide:(UIView*)secondV {
179 | CGFloat x = [self getXStart];
180 | CGFloat y = _yPos > self.height ? _yPos - self.height : _yPos;
181 | [firstV sizeToFit];
182 | CGRect rect = firstV.frame;
183 | rect.origin = CGPointMake(x, y);
184 | rect.size.height = _btnH;
185 | firstV.frame = rect;
186 |
187 | CGFloat btnW = (_winWdt) - _gap*3 - rect.size.width;
188 | CGFloat xPos = rect.origin.x + rect.size.width + _gap;
189 | secondV.frame = CGRectMake( xPos, y, btnW, _btnH);
190 | _yPos += (_btnH + _gap);
191 | }
192 | //(secondV 使用内容宽度, 剩余宽度全部分配给firstV)
193 | - (void) putWide:(UIView*)firstV
194 | andNarrow:(UIView*)secondV {
195 | CGFloat x = [self getXStart];
196 | CGFloat y = _yPos > self.height ? _yPos - self.height : _yPos;
197 |
198 | [secondV sizeToFit];
199 | CGRect rect = secondV.frame;
200 | rect.size.height = _btnH;
201 |
202 | CGFloat slW = (_winWdt) - _gap*3 - rect.size.width;
203 | rect.origin = CGPointMake(slW+x, y);
204 | secondV.frame = rect;
205 |
206 | rect.origin = CGPointMake(x, y);
207 | rect.size.width = slW;
208 | firstV.frame = rect;
209 | _yPos += (_btnH + _gap);
210 | }
211 |
212 | - (void) putLable:(UIView*)lbl
213 | andView:(UIView*)subV{
214 | [self putNarrow:lbl andWide:subV];
215 | }
216 |
217 | - (void) putSlider:(UIView*)sl
218 | andSwitch:(UIView*)sw{
219 | [self putWide:sl andNarrow:sw];
220 | }
221 |
222 | #pragma mark - new and add UI elements
223 |
224 | - (UITextField *)addTextField: (NSString*)text{
225 | UITextField * textF;
226 | textF = [[UITextField alloc] init];
227 | textF.text = text;
228 | textF.borderStyle = UITextBorderStyleRoundedRect;
229 | [self addSubview:textF];
230 | return textF;
231 | }
232 |
233 | - (UIButton *)newButton: (NSString*)title{
234 | UIButton * button;
235 | button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
236 | [button setTitle: title forState: UIControlStateNormal];
237 | button.backgroundColor = [UIColor lightGrayColor];
238 | button.alpha = 0.9;
239 | button.layer.cornerRadius = 10;
240 | button.clipsToBounds = YES;
241 | [self addSubview:button];
242 | return button;
243 | }
244 |
245 | // add to default respond
246 | - (UIButton *)addButton:(NSString*)title{
247 | UIButton * button = [self newButton: title];
248 | [button addTarget:self
249 | action:@selector(onBtn:)
250 | forControlEvents:UIControlEventTouchUpInside];
251 | return button;
252 | }
253 |
254 | - (UIButton *)addButton:(NSString*)title
255 | action:(SEL)action {
256 | UIButton * button = [self newButton: title];
257 | [button addTarget:self
258 | action:action
259 | forControlEvents:UIControlEventTouchUpInside];
260 | return button;
261 | }
262 |
263 | - (UISegmentedControl *)addSegCtrlWithItems: (NSArray *) items {
264 | UISegmentedControl * segC;
265 | segC = [[UISegmentedControl alloc] initWithItems:items];
266 | segC.selectedSegmentIndex = 0;
267 | segC.layer.cornerRadius = 5;
268 | segC.backgroundColor = [UIColor lightGrayColor];
269 | [segC addTarget:self
270 | action:@selector(onSegCtrl:)
271 | forControlEvents:UIControlEventValueChanged];
272 | [self addSubview:segC];
273 | return segC;
274 | }
275 |
276 | - (UILabel *)addLable:(NSString*)title{
277 | UILabel * lbl = [[UILabel alloc] init];
278 | lbl.text = title;
279 | [self addSubview:lbl];
280 | lbl.backgroundColor = [UIColor colorWithWhite:0.8 alpha:0.3];
281 | return lbl;
282 | }
283 |
284 | - (UISwitch *)newSwitch:(BOOL) on{
285 | UISwitch *sw = [[UISwitch alloc] init];
286 | [self addSubview:sw];
287 | sw.on = on;
288 | return sw;
289 | }
290 |
291 | - (UISwitch *)addSwitch:(BOOL) on{
292 | UISwitch *switcher = [self newSwitch:on];
293 | [switcher addTarget:self
294 | action:@selector(onSwitch:)
295 | forControlEvents:UIControlEventValueChanged ];
296 | return switcher;
297 | }
298 |
299 | - (UISlider *)newSliderFrom: (float) minV
300 | To: (float) maxV
301 | Init: (float) iniV {
302 | UISlider *sl = [[UISlider alloc] init];
303 | [self addSubview:sl];
304 | sl.minimumValue = minV;
305 | sl.maximumValue = maxV;
306 | sl.value = iniV;
307 | sl.continuous = NO;
308 | return sl;
309 | }
310 |
311 | - (UISlider *)addSliderFrom: (float) minV
312 | To: (float) maxV
313 | Init: (float) iniV {
314 | UISlider *slider = [self newSliderFrom:minV To:maxV Init:iniV];
315 | [slider addTarget:self
316 | action:@selector(onSlider:)
317 | forControlEvents:UIControlEventValueChanged];
318 | return slider;
319 | }
320 |
321 | - (UISlider *)addSliderFrom: (float) minV
322 | To: (float) maxV
323 | Init: (float) iniV
324 | action: (SEL)action {
325 | UISlider *slider = [self newSliderFrom:minV To:maxV Init:iniV];
326 | [ slider addTarget:self
327 | action:action
328 | forControlEvents:UIControlEventValueChanged ];
329 | return slider;
330 | }
331 |
332 |
333 | - (KSYNameSlider *)addSliderName: (NSString*) name
334 | From: (float) minV
335 | To: (float) maxV
336 | Init: (float) iniV {
337 | KSYNameSlider *sl = [[KSYNameSlider alloc] init];
338 | [self addSubview:sl];
339 | sl.slider.minimumValue = minV;
340 | sl.slider.maximumValue = maxV;
341 | sl.slider.value = iniV;
342 | sl.nameL.text = name;
343 | sl.normalValue = (iniV -minV)/maxV;
344 | sl.valueL.text = [NSString stringWithFormat:@"%d", (int)iniV];
345 | if (iniV <2){
346 | sl.precision = 2;
347 | }
348 | [sl.slider addTarget:self
349 | action:@selector(onSlider:)
350 | forControlEvents:UIControlEventValueChanged ];
351 | weakObj(self);
352 | sl.onSliderBlock = ^(id sender){
353 | [selfWeak onSlider:sender];
354 | };
355 | return sl;
356 | }
357 |
358 | #pragma mark - UI respond
359 |
360 | //UIControlEventTouchUpInside
361 | - (IBAction)onBtn:(id)sender {
362 | if (_onBtnBlock) {
363 | _onBtnBlock(sender);
364 | }
365 | if ([self.superview isKindOfClass:[KSYUIView class]]){
366 | KSYUIView * v = (KSYUIView *)self.superview;
367 | [v onBtn:sender];
368 | }
369 | }
370 | //UIControlEventValueChanged
371 | - (IBAction)onSwitch:(id)sender {
372 | if (_onSwitchBlock){
373 | _onSwitchBlock(sender);
374 | }
375 | if ([self.superview isKindOfClass:[KSYUIView class]]){
376 | KSYUIView * v = (KSYUIView *)self.superview;
377 | [v onSwitch:sender];
378 | }
379 | }
380 | //UIControlEventValueChanged
381 | - (IBAction)onSlider:(id)sender {
382 | if (_onSliderBlock){
383 | _onSliderBlock(sender);
384 | }
385 | if ([self.superview isKindOfClass:[KSYUIView class]]){
386 | KSYUIView * v = (KSYUIView *)self.superview;
387 | [v onSlider:sender];
388 | }
389 | }
390 |
391 | //UIControlEventValueChanged
392 | - (IBAction)onSegCtrl:(id)sender {
393 | if (_onSegCtrlBlock){
394 | _onSegCtrlBlock(sender);
395 | }
396 | if ([self.superview isKindOfClass:[KSYUIView class]]){
397 | KSYUIView * v = (KSYUIView *)self.superview;
398 | [v onSegCtrl:sender];
399 | }
400 | }
401 |
402 | // 获取设备的UUID
403 | + (NSString *) getUuid{
404 | return [[[[UIDevice currentDevice] identifierForVendor] UUIDString] lowercaseString];
405 | }
406 | @end
407 |
408 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // KSYViewController.h
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 | #import "KSYUIVC.h"
11 |
12 | @interface KSYViewController : KSYUIVC
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/KSYViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // KSYViewController.m
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 | #import "KSYViewController.h"
9 | #import "KSYAirView.h"
10 | #import "KSYAirStreamKit.h"
11 | #import
12 |
13 | @interface KSYViewController () {
14 | KSYAirStreamKit * _kit;
15 | KSYAirView * _airView;
16 | }
17 |
18 | @end
19 |
20 | @implementation KSYViewController
21 |
22 |
23 | - (void)viewDidLoad {
24 | [super viewDidLoad];
25 | _airView = [[KSYAirView alloc] init];
26 | self.layoutView = _airView;
27 | [self.view addSubview:_airView];
28 | __weak typeof (self) selfWeak = self;
29 | _airView.onBtnBlock = ^(id sender){
30 | [selfWeak onBtnPress:sender];
31 | };
32 |
33 |
34 | _kit = [[KSYAirStreamKit alloc] init];
35 | _kit.delegate = self;
36 | NSNotificationCenter* dc = [NSNotificationCenter defaultCenter];
37 | [dc addObserver:self
38 | selector:@selector(onStreamStateChange)
39 | name:KSYStreamStateDidChangeNotification
40 | object:nil];
41 | [dc addObserver:self
42 | selector:@selector(onNetStateEvent)
43 | name:KSYNetStateEventNotification
44 | object:nil];
45 | }
46 |
47 | - (void)didReceiveMemoryWarning {
48 | [super didReceiveMemoryWarning];
49 | // Dispose of any resources that can be recreated.
50 | }
51 | - (void)viewDidAppear:(BOOL)animated {
52 | [self layoutUI];
53 | }
54 | - (BOOL)shouldAutorotate {
55 | [self layoutUI];
56 | return YES;
57 | }
58 | - (void) viewWillTransitionToSize:(CGSize)size
59 | withTransitionCoordinator:(id) coordinator{
60 | [coordinator animateAlongsideTransition:^(id context) {
61 | }completion:^(id context) {
62 | [self layoutUI];
63 | }];
64 | }
65 |
66 | - (IBAction)onBtnPress:(id)sender {
67 | if (sender == _airView.btn) {
68 | if (_kit == nil) {
69 | return;
70 | }
71 | _airView.btn.selected = !_airView.btn.selected;
72 | if (_airView.btn.selected) {
73 | _kit.airCfg = [_airView airCfg];
74 | _kit.videoBitrate = (int)_airView.videoBitrateUI.value;
75 | _kit.streamUrl = _airView.txtAddr.text;
76 | [_kit startService];
77 | _airView.airState = @"开始启动服务...";
78 | }
79 | else {
80 | [_kit stopService];
81 | _airView.airState = @"开始停止服务...";
82 | }
83 | }
84 | }
85 |
86 | - (void) dealloc {
87 | [[NSNotificationCenter defaultCenter] removeObserver:self];
88 | }
89 |
90 | - (void) onStreamStateChange {
91 | if (_kit.streamerBase){
92 | _airView.strState = [_kit.streamerBase getCurStreamStateName];
93 | if (_kit.streamerBase.streamState == KSYStreamStateError) {
94 | _airView.strState = [_kit.streamerBase getCurKSYStreamErrorCodeName];
95 | }
96 | NSLog(@"stream State %@", _airView.strState);
97 | }
98 | }
99 |
100 | - (void) onNetStateEvent {
101 | if (_kit.streamerBase){
102 | switch (_kit.streamerBase.netStateCode) {
103 | case KSYNetStateCode_SEND_PACKET_SLOW: { // 1
104 | break;
105 | }
106 | case KSYNetStateCode_EST_BW_RAISE: { // 2
107 | break;
108 | }
109 | case KSYNetStateCode_EST_BW_DROP: { // 3
110 | break;
111 | }
112 | case KSYNetStateCode_REACHABLE: {
113 | NSLog(@"network reachable");
114 | }
115 | case KSYNetStateCode_UNREACHABLE: {
116 | NSLog(@"network unreachable");
117 | }
118 | default:break;
119 | }
120 | }
121 | }
122 |
123 | #pragma mark - KSYAirDelegate
124 | - (void) didStartMirroring:(KSYAirTunesServer *)server {
125 | _airView.airState = @"mirroring";
126 | }
127 | - (void)mirroringErrorDidOcccur:(KSYAirTunesServer *)server withError:(NSError *)error {
128 | _airView.airState = error.localizedDescription;
129 | _airView.btn.selected = NO;
130 | [_kit stopService];
131 | }
132 | - (void)didStopMirroring:(KSYAirTunesServer *)server {
133 | _airView.airState = @"idle";
134 | }
135 | @end
136 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/demo/KSYAirStreamer/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 | #import "KSYAppDelegate.h"
11 |
12 | int main(int argc, char * argv[])
13 | {
14 | @autoreleasepool {
15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([KSYAppDelegate class]));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/demo/Podfile:
--------------------------------------------------------------------------------
1 | inhibit_all_warnings!
2 | install! 'cocoapods', :deterministic_uuids => false
3 | use_frameworks!
4 |
5 | target 'KSYAirStreamer_Example' do
6 | pod 'Bugly'
7 | pod 'GPUImage'
8 | pod 'libksygpulive/libksygpulive', '~> 2.9.2'
9 | pod 'KSYAirStreamer', :path => '../'
10 |
11 | target 'KSYAirStreamer_Tests' do
12 | inherit! :search_paths
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/demo/Tests/Tests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/demo/Tests/Tests-Prefix.pch:
--------------------------------------------------------------------------------
1 | // The contents of this file are implicitly included at the beginning of every test case source file.
2 |
3 | #ifdef __OBJC__
4 |
5 |
6 |
7 | #endif
8 |
--------------------------------------------------------------------------------
/demo/Tests/Tests.m:
--------------------------------------------------------------------------------
1 | //
2 | // KSYAirStreamerTests.m
3 | // KSYAirStreamerTests
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 |
9 | @import XCTest;
10 |
11 | @interface Tests : XCTestCase
12 |
13 | @end
14 |
15 | @implementation Tests
16 |
17 | - (void)setUp
18 | {
19 | [super setUp];
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | - (void)tearDown
24 | {
25 | // Put teardown code here. This method is called after the invocation of each test method in the class.
26 | [super tearDown];
27 | }
28 |
29 | - (void)testExample
30 | {
31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__);
32 | }
33 |
34 | @end
35 |
36 |
--------------------------------------------------------------------------------
/demo/Tests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/docs/html/Classes/KSYAirStreamKit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KSYAirStreamKit Class Reference
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Kingsoft
22 |
23 |
24 |
25 |
26 |
27 |
99 |
100 |
101 |
102 |
103 |
104 | KSYAirStreamKit Class Reference
105 |
106 |
107 |
108 |
109 | Inherits from |
110 | NSObject |
111 |
112 | Declared in |
113 | KSYAirStreamKit.h |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
Overview
123 |
airplay 镜像录屏 + KSYStreamerBase 推流
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
带鉴权的构造接收server实例
151 |
152 |
153 |
154 |
155 |
- (instancetype)initWithToken:(NSString *)token error:(NSError **)error
156 |
157 |
158 |
159 |
160 |
Parameters
161 |
162 |
163 |
164 | token |
165 | 鉴权信息, 请联系商务获取 |
166 |
167 |
168 |
169 | error |
170 | 鉴权过程中的错误信息 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
Return Value
180 |
新构造的实例
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
Declared In
195 |
KSYAirStreamKit.h
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
215 |
216 |
217 |
218 |
@property KSYAirTunesServer *airTunesServer
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
Declared In
236 |
KSYAirStreamKit.h
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
256 |
257 |
258 |
259 |
@property KSYAirTunesConfig *airCfg
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
Declared In
277 |
KSYAirStreamKit.h
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
297 |
298 |
299 |
300 |
@property KSYAudioCap *aCapDev
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
Declared In
318 |
KSYAirStreamKit.h
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
音频mixer (音频buffer)
337 |
338 |
339 |
340 |
341 |
@property KSYAudioMixer *aMixer
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
Declared In
359 |
KSYAirStreamKit.h
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
379 |
380 |
381 |
382 |
@property NSString *streamUrl
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
Declared In
400 |
KSYAirStreamKit.h
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
420 |
421 |
422 |
423 |
@property int videoBitrate
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
Declared In
441 |
KSYAirStreamKit.h
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
461 |
462 |
463 |
464 |
@property KSYStreamerBase *streamerBase
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
Declared In
482 |
KSYAirStreamKit.h
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
502 |
503 |
504 |
505 |
@property (nonatomic, weak) id<KSYAirDelegate> delegate
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
Declared In
523 |
KSYAirStreamKit.h
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
启动镜像服务, 当成功建立airplay镜像连接时, 再启动音频采集和推流
542 |
543 |
544 |
545 |
546 |
- (void)startService
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
Declared In
564 |
KSYAirStreamKit.h
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
584 |
585 |
586 |
587 |
- (void)stopService
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
Declared In
605 |
KSYAirStreamKit.h
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
--------------------------------------------------------------------------------
/docs/html/Classes/KSYAirTunesConfig.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KSYAirTunesConfig Class Reference
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Kingsoft
22 |
23 |
24 |
25 |
26 |
27 |
89 |
90 |
91 |
92 |
93 |
94 | KSYAirTunesConfig Class Reference
95 |
96 |
97 |
98 |
99 | Inherits from |
100 | NSObject |
101 |
102 | Declared in |
103 | KSYAirTunesServer.h |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
Overview
113 |
airplay的配置信息
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
AirPlay 设备的名字
141 |
142 |
143 |
144 |
145 |
@property (nonatomic, copy) NSString *airplayName
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
Declared In
163 |
KSYAirTunesServer.h
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
接收设备的尺寸 (默认为 960x960)
182 | 如果videoSize的宽高相同, 则横竖屏旋转时,输出的分辨率保持不变;
183 | 当宽高不同时, 横竖屏旋转后, 高度保持不变, 宽度会跟随设备的屏幕比例变化
184 | 请注意,宽高比需要和屏幕的比例相同, 否则内部自动按照画面小的数值计算
185 |
186 |
187 |
188 |
189 |
@property (nonatomic, assign) CGSize videoSize
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
Declared In
207 |
KSYAirTunesServer.h
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
是否需要在宽高不同时, 在屏幕两边填上黑边 (默认为 NO)
226 |
227 |
228 |
229 |
230 |
@property (nonatomic, assign) BOOL padding
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
Declared In
248 |
KSYAirTunesServer.h
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
希望接收到ios发送端的视频帧率 (有效值为 10, 15, 30), 默认为30
267 |
268 |
269 |
270 |
271 |
@property (nonatomic, assign) int framerate
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
Declared In
289 |
KSYAirTunesServer.h
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
设置airtunes 服务的监听端口, 0 表示系统自动分配
308 |
309 |
310 |
311 |
312 |
@property (nonatomic, assign) short airTunesPort
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
Declared In
330 |
KSYAirTunesServer.h
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
设置视频数据的接收端口,默认是7100, 当7100被占用时, 会尝试+1 尝试10次, 如果仍然失败报告端口冲突
349 |
350 |
351 |
352 |
353 |
@property (nonatomic, assign) short airVideoPort
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
Declared In
371 |
KSYAirTunesServer.h
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
设备的mac地址, 默认随机生成,(长度为6字节)
390 |
391 |
392 |
393 |
394 |
@property (nonatomic, copy) NSData *macAddr
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
Declared In
412 |
KSYAirTunesServer.h
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
AirPlay接收数据的解码器(默认为KSYAirVideoDecoder_SOFTWARE)
431 |
432 |
433 |
434 |
435 |
@property (nonatomic, assign) KSYAirVideoDecoder videoDecoder
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
Declared In
453 |
KSYAirTunesServer.h
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
--------------------------------------------------------------------------------
/docs/html/Classes/KSYAirTunesServer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KSYAirTunesServer Class Reference
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Kingsoft
22 |
23 |
24 |
25 |
26 |
27 |
95 |
96 |
97 |
98 |
99 |
100 | KSYAirTunesServer Class Reference
101 |
102 |
103 |
104 |
105 | Inherits from |
106 | NSObject |
107 |
108 | Declared in |
109 | KSYAirTunesServer.h |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
Overview
119 |
Airplay 接收server
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
带鉴权的构造接收server实例
147 |
148 |
149 |
150 |
151 |
- (instancetype)initWithToken:(NSString *)token error:(NSError **)error
152 |
153 |
154 |
155 |
156 |
Parameters
157 |
158 |
159 |
160 | token |
161 | 鉴权信息, 请联系商务获取 |
162 |
163 |
164 |
165 | error |
166 | 鉴权过程中的错误信息 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
Return Value
176 |
新构造的实例
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
Declared In
191 |
KSYAirTunesServer.h
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
211 |
212 |
213 |
214 |
@property (nonatomic, readonly) NSDate *expireDate
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
Declared In
232 |
KSYAirTunesServer.h
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
252 |
253 |
254 |
255 |
@property (nonatomic, copy) void ( ^ ) ( CVPixelBufferRef pixelBuffer , CMTime timeInfo ) videoProcessingCallback
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
Declared In
273 |
KSYAirTunesServer.h
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
293 |
294 |
295 |
296 |
@property (nonatomic, copy) void ( ^ ) ( NSData *data , BOOL bParameterSet , CMTime timeInfo ) videoBitStreamCallback
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
Declared In
314 |
KSYAirTunesServer.h
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
334 |
335 |
336 |
337 |
@property (nonatomic, weak) id<KSYAirDelegate> delegate
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
Declared In
355 |
KSYAirTunesServer.h
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
375 |
376 |
377 |
378 |
@property (nonatomic, readonly) KSYAirState airState
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
Declared In
396 |
KSYAirTunesServer.h
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
416 |
417 |
418 |
419 |
- (void)startServerWithCfg:(KSYAirTunesConfig *)cfg
420 |
421 |
422 |
423 |
424 |
Parameters
425 |
426 |
427 |
428 | cfg |
429 | 服务的配置信息 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
Declared In
449 |
KSYAirTunesServer.h
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
469 |
470 |
471 |
472 |
- (void)stopServer
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
Declared In
490 |
KSYAirTunesServer.h
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
查询 errorcode 的名称
509 |
510 |
511 |
512 |
513 |
- (NSString *)getKSYAirErrorName:(KSYAirErrorCode)errCode
514 |
515 |
516 |
517 |
518 |
Parameters
519 |
520 |
521 |
522 | errCode |
523 | 错误码 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
Return Value
533 |
错误码的名称字符串
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
Declared In
548 |
KSYAirTunesServer.h
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
--------------------------------------------------------------------------------
/docs/html/Classes/KSYAudioCap.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KSYAudioCap Class Reference
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Kingsoft
22 |
23 |
24 |
25 |
26 |
27 |
67 |
68 |
69 |
70 |
71 |
72 | KSYAudioCap Class Reference
73 |
74 |
75 |
76 |
77 | Inherits from |
78 | KSYAUAudioCapture |
79 |
80 | Declared in |
81 | KSYAirStreamKit.h |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
Overview
91 |
音频采集模块, 主要是重写 isHeadsetPluggedIn方法, 避免音频采集的问题
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/docs/html/Constants/KSYAirErrorCode.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KSYAirErrorCode Constants Reference
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Kingsoft
22 |
23 |
24 |
25 |
26 |
27 |
57 |
58 |
59 |
60 |
61 |
62 | KSYAirErrorCode Constants Reference
63 |
64 |
65 |
66 |
67 | Declared in |
68 | KSYAirTunesServer.h |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | KSYAirErrorCode
79 |
80 |
81 |
84 |
85 |
86 |
107 |
108 |
109 |
Constants
110 |
111 |
112 | KSYAirErrorCodePortConflict
113 | -
114 |
115 |
116 |
AirPlay端口冲突
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | Declared In KSYAirTunesServer.h
.
125 |
126 |
127 |
128 |
129 | KSYAirErrorCodeNetworkDisconnection
130 | -
131 |
132 |
133 |
网络未连接
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | Declared In KSYAirTunesServer.h
.
142 |
143 |
144 |
145 |
146 | KSYAirErrorCodeAirPlaySelectTimeout
147 | -
148 |
149 |
150 |
连接AirPlay超时
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | Declared In KSYAirTunesServer.h
.
159 |
160 |
161 |
162 |
163 | KSYAirErrorCodeConnectFailed
164 | -
165 |
166 |
167 |
连接失败
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 | Declared In KSYAirTunesServer.h
.
176 |
177 |
178 |
179 |
180 | KSYAirErrorCodeConnectBreak
181 | -
182 |
183 |
184 |
连接断开
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | Declared In KSYAirTunesServer.h
.
193 |
194 |
195 |
196 |
197 | KSYAirErrorCodeOther
198 | -
199 |
200 |
201 |
其他错误
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 | Declared In KSYAirTunesServer.h
.
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
Declared In
226 |
KSYAirTunesServer.h
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
--------------------------------------------------------------------------------
/docs/html/Constants/KSYAirState.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KSYAirState Constants Reference
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Kingsoft
22 |
23 |
24 |
25 |
26 |
27 |
57 |
58 |
59 |
60 |
61 |
62 | KSYAirState Constants Reference
63 |
64 |
65 |
66 |
67 | Declared in |
68 | KSYAirTunesServer.h |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | KSYAirState
79 |
80 |
81 |
84 |
85 |
86 |
105 |
106 |
107 |
Constants
108 |
109 |
110 | KSYAirState_Idle
111 | -
112 |
113 |
114 |
初始状态, 空闲
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Declared In KSYAirTunesServer.h
.
123 |
124 |
125 |
126 |
127 | KSYAirState_Connecting
128 | -
129 |
130 |
131 |
连接中
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | Declared In KSYAirTunesServer.h
.
140 |
141 |
142 |
143 |
144 | KSYAirState_Mirroring
145 | -
146 |
147 |
148 |
镜像中, 连接完成了
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 | Declared In KSYAirTunesServer.h
.
157 |
158 |
159 |
160 |
161 | KSYAirState_Disconnecting
162 | -
163 |
164 |
165 |
断开连接中
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | Declared In KSYAirTunesServer.h
.
174 |
175 |
176 |
177 |
178 | KSYAirState_Error
179 | -
180 |
181 |
182 |
发生错误了
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 | Declared In KSYAirTunesServer.h
.
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
Declared In
207 |
KSYAirTunesServer.h
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
--------------------------------------------------------------------------------
/docs/html/Constants/KSYAirVideoDecoder.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KSYAirVideoDecoder Constants Reference
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Kingsoft
22 |
23 |
24 |
25 |
26 |
27 |
57 |
58 |
59 |
60 |
61 |
62 | KSYAirVideoDecoder Constants Reference
63 |
64 |
65 |
66 |
67 | Declared in |
68 | KSYAirTunesServer.h |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | KSYAirVideoDecoder
79 |
80 |
81 |
84 |
85 |
86 |
101 |
102 |
103 |
Constants
104 |
105 |
106 | KSYAirVideoDecoder_SOFTWARE
107 | -
108 |
109 |
110 |
软解, 解码后画面通过videoProcessingCallback回调给出
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | Declared In KSYAirTunesServer.h
.
119 |
120 |
121 |
122 |
123 | KSYAirVideoDecoder_VIDEOTOOLBOX
124 | -
125 |
126 |
127 |
硬解, 解码后画面通过videoProcessingCallback回调给出
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | Declared In KSYAirTunesServer.h
.
136 |
137 |
138 |
139 |
140 | KSYAirVideoDecoder_NONE
141 | -
142 |
143 |
144 |
无解码,将接收到的264码流直接通过videoBitStreamCallback回调函数给出
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | Declared In KSYAirTunesServer.h
.
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
Declared In
169 |
KSYAirTunesServer.h
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
--------------------------------------------------------------------------------
/docs/html/Protocols/KSYAirDelegate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KSYAirDelegate Protocol Reference
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Kingsoft
22 |
23 |
24 |
25 |
26 |
27 |
79 |
80 |
81 |
82 |
83 |
84 | KSYAirDelegate Protocol Reference
85 |
86 |
87 |
88 |
89 | Conforms to |
90 | NSObject |
91 |
92 | Declared in |
93 | KSYAirTunesServer.h |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
Overview
103 |
airplay 镜像状态变化的代理
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
airplay 镜像成功开始了
131 |
132 |
133 |
134 |
135 |
- (void)didStartMirroring:(KSYAirTunesServer *)server
136 |
137 |
138 |
139 |
140 |
Parameters
141 |
142 |
143 |
144 | server |
145 | airplay服务对象 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
Declared In
165 |
KSYAirTunesServer.h
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
airplay 镜像 遇到错误了
184 |
185 |
186 |
187 |
188 |
- (void)mirroringErrorDidOcccur:(KSYAirTunesServer *)server withError:(NSError *)error
189 |
190 |
191 |
192 |
193 |
Parameters
194 |
195 |
196 |
197 | server |
198 | airplay服务对象 |
199 |
200 |
201 |
202 | error |
203 | 遇到的错误, code 参见 KSYAirErrorCode的定义 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
Declared In
223 |
KSYAirTunesServer.h
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
airplay 镜像成功结束了
242 |
243 |
244 |
245 |
246 |
- (void)didStopMirroring:(KSYAirTunesServer *)server
247 |
248 |
249 |
250 |
251 |
Parameters
252 |
253 |
254 |
255 | server |
256 | airplay服务对象 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
Declared In
276 |
KSYAirTunesServer.h
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
--------------------------------------------------------------------------------
/docs/html/css/scss/_index.scss:
--------------------------------------------------------------------------------
1 | .index-container {
2 | -webkit-flex-direction: column;
3 | flex-direction: column;
4 |
5 | @media (min-width: $desktop-min-width) {
6 | display: flex;
7 | -webkit-flex-direction: row;
8 | flex-direction: row;
9 | -webkit-flex-wrap: wrap;
10 | flex-wrap: wrap;
11 | }
12 |
13 | .index-column {
14 | -webkit-flex: 1 1 33%;
15 | flex: 1 1 33%;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/docs/html/css/scss/_layout.scss:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | .clear {
6 | clear: both;
7 | }
8 |
9 | .clearfix {
10 | &:before, &:after {
11 | clear: both;
12 | display: table;
13 | content: "";
14 | }
15 | }
16 |
17 | .xcode .hide-in-xcode {
18 | display: none;
19 | }
20 |
21 | body {
22 | font: 62.5% $body-font;
23 | background: $body-background;
24 |
25 | @media (max-width: $mobile-max-width) {
26 | background-color: $content-background;
27 | }
28 | }
29 |
30 | h1, h2, h3 {
31 | font-weight: 300;
32 | color: #808080;
33 | }
34 |
35 | h1 {
36 | font-size: 2em;
37 | color: #000;
38 | }
39 |
40 | h4 {
41 | font-size: 13px;
42 | line-height: 1.5;
43 | margin: 21px 0 0 0;
44 | }
45 |
46 | a {
47 | color: $tint-color;
48 | text-decoration: none;
49 | }
50 |
51 | pre, code {
52 | font-family: $code-font;
53 | word-wrap: break-word;
54 | }
55 |
56 | pre > code, .method-declaration code {
57 | display: inline-block;
58 | font-size: .85em;
59 | padding: 4px 0 4px 10px;
60 | border-left: 5px solid rgba(0, 155, 51, .2);
61 |
62 | &:before {
63 | content: "Objective-C";
64 | display: block;
65 |
66 | font: 9px/1 $body-font;
67 | color: #009b33;
68 | text-transform: uppercase;
69 | letter-spacing: 2px;
70 | padding-bottom: 6px;
71 | }
72 | }
73 |
74 | pre > code {
75 | font-size: inherit;
76 | }
77 |
78 | table, th, td {
79 | border: 1px solid #e9e9e9;
80 | }
81 |
82 | table {
83 | width: 100%;
84 | }
85 |
86 | th, td {
87 | padding: 7px;
88 |
89 | > :first-child {
90 | margin-top: 0;
91 | }
92 |
93 | > :last-child {
94 | margin-bottom: 0;
95 | }
96 | }
97 |
98 | .container {
99 | @extend .clearfix;
100 |
101 | max-width: 980px;
102 | padding: 0 10px;
103 | margin: 0 auto;
104 |
105 | @media (max-width: $mobile-max-width) {
106 | padding: 0;
107 | }
108 | }
109 |
110 | header {
111 | position: fixed;
112 | top: 0;
113 | left: 0;
114 | width: 100%;
115 | z-index: 2;
116 |
117 | background: #414141;
118 | color: #fff;
119 | font-size: 1.1em;
120 | line-height: 25px;
121 | letter-spacing: .05em;
122 |
123 | #library-title {
124 | float: left;
125 | }
126 |
127 | #developer-home {
128 | float: right;
129 | }
130 |
131 | h1 {
132 | font-size: inherit;
133 | font-weight: inherit;
134 | margin: 0;
135 | }
136 |
137 | p {
138 | margin: 0;
139 | }
140 |
141 | h1, a {
142 | color: inherit;
143 | }
144 |
145 | @media (max-width: $mobile-max-width) {
146 | .container {
147 | padding: 0 10px;
148 | }
149 | }
150 | }
151 |
152 | aside {
153 | position: fixed;
154 | top: 25px;
155 | left: 0;
156 | width: 100%;
157 | height: 25px;
158 | z-index: 2;
159 |
160 | font-size: 1.1em;
161 |
162 | #header-buttons {
163 | background: rgba(255, 255, 255, .8);
164 | margin: 0 1px;
165 | padding: 0;
166 | list-style: none;
167 | text-align: right;
168 | line-height: 32px;
169 |
170 | li {
171 | display: inline-block;
172 | cursor: pointer;
173 | padding: 0 10px;
174 | }
175 |
176 | label, select {
177 | cursor: inherit;
178 | }
179 |
180 | #on-this-page {
181 | position: relative;
182 |
183 | .chevron {
184 | display: inline-block;
185 | width: 14px;
186 | height: 4px;
187 | position: relative;
188 |
189 | .chevy {
190 | background: #878787;
191 | height: 2px;
192 | position: absolute;
193 | width: 10px;
194 |
195 | &.chevron-left {
196 | left: 0;
197 | transform: rotateZ(45deg) scale(0.6);
198 | }
199 |
200 | &.chevron-right {
201 | right: 0;
202 | transform: rotateZ(-45deg) scale(0.6);
203 | }
204 | }
205 | }
206 |
207 | #jump-to {
208 | opacity: 0;
209 | font-size: 16px;
210 |
211 | position: absolute;
212 | top: 5px;
213 | left: 0;
214 | width: 100%;
215 | height: 100%;
216 | }
217 | }
218 | }
219 | }
220 |
221 | article {
222 | margin-top: 25px;
223 |
224 | #content {
225 | @extend .clearfix;
226 |
227 | background: $content-background;
228 | border: 1px solid $content-border;
229 | padding: 15px 25px 30px 25px;
230 |
231 | font-size: 1.4em;
232 | line-height: 1.45;
233 |
234 | position: relative;
235 |
236 | @media (max-width: $mobile-max-width) {
237 | padding: 15px 10px 20px 10px;
238 | border: none;
239 | }
240 |
241 | .navigation-top {
242 | position: absolute;
243 | top: 15px;
244 | right: 25px;
245 | }
246 |
247 | .title {
248 | margin: 21px 0 0 0;
249 | padding: 15px 0;
250 | }
251 |
252 | p {
253 | color: #414141;
254 | margin: 0 0 15px 0;
255 | }
256 |
257 | th, td {
258 | p:last-child {
259 | margin-bottom: 0;
260 | }
261 | }
262 |
263 | main {
264 | ul {
265 | list-style: none;
266 | margin-left: 24px;
267 | margin-bottom: 12px;
268 | padding: 0;
269 |
270 | li {
271 | position: relative;
272 | padding-left: 1.3em;
273 |
274 | &:before {
275 | content: "\02022";
276 |
277 | color: #414141;
278 | font-size: 1.08em;
279 | line-height: 1;
280 |
281 | position: absolute;
282 | left: 0;
283 | padding-top: 2px;
284 | }
285 | }
286 | }
287 | }
288 |
289 | footer {
290 | @extend .clearfix;
291 |
292 | .footer-copyright {
293 | margin: 70px 25px 10px 0;
294 | }
295 |
296 | p {
297 | font-size: .71em;
298 | color: #a0a0a0;
299 | }
300 | }
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/docs/html/css/scss/_normalize.scss:
--------------------------------------------------------------------------------
1 | /* ==========================================================================
2 | Normalize.scss settings
3 | ========================================================================== */
4 | /**
5 | * Includes legacy browser support IE6/7
6 | *
7 | * Set to false if you want to drop support for IE6 and IE7
8 | */
9 |
10 | $legacy_browser_support: false !default;
11 |
12 | /* Base
13 | ========================================================================== */
14 |
15 | /**
16 | * 1. Set default font family to sans-serif.
17 | * 2. Prevent iOS text size adjust after orientation change, without disabling
18 | * user zoom.
19 | * 3. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using
20 | * `em` units.
21 | */
22 |
23 | html {
24 | font-family: sans-serif; /* 1 */
25 | -ms-text-size-adjust: 100%; /* 2 */
26 | -webkit-text-size-adjust: 100%; /* 2 */
27 | @if $legacy_browser_support {
28 | *font-size: 100%; /* 3 */
29 | }
30 | }
31 |
32 | /**
33 | * Remove default margin.
34 | */
35 |
36 | body {
37 | margin: 0;
38 | }
39 |
40 | /* HTML5 display definitions
41 | ========================================================================== */
42 |
43 | /**
44 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
45 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
46 | * and Firefox.
47 | * Correct `block` display not defined for `main` in IE 11.
48 | */
49 |
50 | article,
51 | aside,
52 | details,
53 | figcaption,
54 | figure,
55 | footer,
56 | header,
57 | hgroup,
58 | main,
59 | menu,
60 | nav,
61 | section,
62 | summary {
63 | display: block;
64 | }
65 |
66 | /**
67 | * 1. Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
68 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
69 | */
70 |
71 | audio,
72 | canvas,
73 | progress,
74 | video {
75 | display: inline-block; /* 1 */
76 | vertical-align: baseline; /* 2 */
77 | @if $legacy_browser_support {
78 | *display: inline;
79 | *zoom: 1;
80 | }
81 | }
82 |
83 | /**
84 | * Prevents modern browsers from displaying `audio` without controls.
85 | * Remove excess height in iOS 5 devices.
86 | */
87 |
88 | audio:not([controls]) {
89 | display: none;
90 | height: 0;
91 | }
92 |
93 | /**
94 | * Address `[hidden]` styling not present in IE 8/9/10.
95 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
96 | */
97 |
98 | [hidden],
99 | template {
100 | display: none;
101 | }
102 |
103 | /* Links
104 | ========================================================================== */
105 |
106 | /**
107 | * Remove the gray background color from active links in IE 10.
108 | */
109 |
110 | a {
111 | background-color: transparent;
112 | }
113 |
114 | /**
115 | * Improve readability when focused and also mouse hovered in all browsers.
116 | */
117 |
118 | a {
119 | &:active, &:hover {
120 | outline: 0;
121 | };
122 | }
123 |
124 | /* Text-level semantics
125 | ========================================================================== */
126 |
127 | /**
128 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
129 | */
130 |
131 | abbr[title] {
132 | border-bottom: 1px dotted;
133 | }
134 |
135 | /**
136 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
137 | */
138 |
139 | b,
140 | strong {
141 | font-weight: bold;
142 | }
143 |
144 | @if $legacy_browser_support {
145 | blockquote {
146 | margin: 1em 40px;
147 | }
148 | }
149 |
150 | /**
151 | * Address styling not present in Safari and Chrome.
152 | */
153 |
154 | dfn {
155 | font-style: italic;
156 | }
157 |
158 | /**
159 | * Address variable `h1` font-size and margin within `section` and `article`
160 | * contexts in Firefox 4+, Safari, and Chrome.
161 | */
162 |
163 | h1 {
164 | font-size: 2em;
165 | margin: 0.67em 0;
166 | }
167 |
168 | @if $legacy_browser_support {
169 | h2 {
170 | font-size: 1.5em;
171 | margin: 0.83em 0;
172 | }
173 |
174 | h3 {
175 | font-size: 1.17em;
176 | margin: 1em 0;
177 | }
178 |
179 | h4 {
180 | font-size: 1em;
181 | margin: 1.33em 0;
182 | }
183 |
184 | h5 {
185 | font-size: 0.83em;
186 | margin: 1.67em 0;
187 | }
188 |
189 | h6 {
190 | font-size: 0.67em;
191 | margin: 2.33em 0;
192 | }
193 | }
194 |
195 | /**
196 | * Addresses styling not present in IE 8/9.
197 | */
198 |
199 | mark {
200 | background: #ff0;
201 | color: #000;
202 | }
203 |
204 | @if $legacy_browser_support {
205 |
206 | /**
207 | * Addresses margins set differently in IE 6/7.
208 | */
209 |
210 | p,
211 | pre {
212 | *margin: 1em 0;
213 | }
214 |
215 | /*
216 | * Addresses CSS quotes not supported in IE 6/7.
217 | */
218 |
219 | q {
220 | *quotes: none;
221 | }
222 |
223 | /*
224 | * Addresses `quotes` property not supported in Safari 4.
225 | */
226 |
227 | q:before,
228 | q:after {
229 | content: '';
230 | content: none;
231 | }
232 | }
233 |
234 | /**
235 | * Address inconsistent and variable font size in all browsers.
236 | */
237 |
238 | small {
239 | font-size: 80%;
240 | }
241 |
242 | /**
243 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
244 | */
245 |
246 | sub,
247 | sup {
248 | font-size: 75%;
249 | line-height: 0;
250 | position: relative;
251 | vertical-align: baseline;
252 | }
253 |
254 | sup {
255 | top: -0.5em;
256 | }
257 |
258 | sub {
259 | bottom: -0.25em;
260 | }
261 |
262 | @if $legacy_browser_support {
263 |
264 | /* ==========================================================================
265 | Lists
266 | ========================================================================== */
267 |
268 | /*
269 | * Addresses margins set differently in IE 6/7.
270 | */
271 |
272 | dl,
273 | menu,
274 | ol,
275 | ul {
276 | *margin: 1em 0;
277 | }
278 |
279 | dd {
280 | *margin: 0 0 0 40px;
281 | }
282 |
283 | /*
284 | * Addresses paddings set differently in IE 6/7.
285 | */
286 |
287 | menu,
288 | ol,
289 | ul {
290 | *padding: 0 0 0 40px;
291 | }
292 |
293 | /*
294 | * Corrects list images handled incorrectly in IE 7.
295 | */
296 |
297 | nav ul,
298 | nav ol {
299 | *list-style: none;
300 | *list-style-image: none;
301 | }
302 |
303 | }
304 |
305 | /* Embedded content
306 | ========================================================================== */
307 |
308 | /**
309 | * 1. Remove border when inside `a` element in IE 8/9/10.
310 | * 2. Improves image quality when scaled in IE 7.
311 | */
312 |
313 | img {
314 | border: 0;
315 | @if $legacy_browser_support {
316 | *-ms-interpolation-mode: bicubic; /* 2 */
317 | }
318 | }
319 |
320 | /**
321 | * Correct overflow not hidden in IE 9/10/11.
322 | */
323 |
324 | svg:not(:root) {
325 | overflow: hidden;
326 | }
327 |
328 | /* Grouping content
329 | ========================================================================== */
330 |
331 | /**
332 | * Address margin not present in IE 8/9 and Safari.
333 | */
334 |
335 | figure {
336 | margin: 1em 40px;
337 | }
338 |
339 | /**
340 | * Address differences between Firefox and other browsers.
341 | */
342 |
343 | hr {
344 | -moz-box-sizing: content-box;
345 | box-sizing: content-box;
346 | height: 0;
347 | }
348 |
349 | /**
350 | * Contain overflow in all browsers.
351 | */
352 |
353 | pre {
354 | overflow: auto;
355 | }
356 |
357 | /**
358 | * Address odd `em`-unit font size rendering in all browsers.
359 | * Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
360 | */
361 |
362 | code,
363 | kbd,
364 | pre,
365 | samp {
366 | font-family: monospace, monospace;
367 | @if $legacy_browser_support {
368 | _font-family: 'courier new', monospace;
369 | }
370 | font-size: 1em;
371 | }
372 |
373 | /* Forms
374 | ========================================================================== */
375 |
376 | /**
377 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
378 | * styling of `select`, unless a `border` property is set.
379 | */
380 |
381 | /**
382 | * 1. Correct color not being inherited.
383 | * Known issue: affects color of disabled elements.
384 | * 2. Correct font properties not being inherited.
385 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
386 | * 4. Improves appearance and consistency in all browsers.
387 | */
388 |
389 | button,
390 | input,
391 | optgroup,
392 | select,
393 | textarea {
394 | color: inherit; /* 1 */
395 | font: inherit; /* 2 */
396 | margin: 0; /* 3 */
397 | @if $legacy_browser_support {
398 | vertical-align: baseline; /* 3 */
399 | *vertical-align: middle; /* 3 */
400 | }
401 | }
402 |
403 | /**
404 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
405 | */
406 |
407 | button {
408 | overflow: visible;
409 | }
410 |
411 | /**
412 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
413 | * All other form control elements do not inherit `text-transform` values.
414 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
415 | * Correct `select` style inheritance in Firefox.
416 | */
417 |
418 | button,
419 | select {
420 | text-transform: none;
421 | }
422 |
423 | /**
424 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
425 | * and `video` controls.
426 | * 2. Correct inability to style clickable `input` types in iOS.
427 | * 3. Improve usability and consistency of cursor style between image-type
428 | * `input` and others.
429 | * 4. Removes inner spacing in IE 7 without affecting normal text inputs.
430 | * Known issue: inner spacing remains in IE 6.
431 | */
432 |
433 | button,
434 | html input[type="button"], /* 1 */
435 | input[type="reset"],
436 | input[type="submit"] {
437 | -webkit-appearance: button; /* 2 */
438 | cursor: pointer; /* 3 */
439 | @if $legacy_browser_support {
440 | *overflow: visible; /* 4 */
441 | }
442 | }
443 |
444 | /**
445 | * Re-set default cursor for disabled elements.
446 | */
447 |
448 | button[disabled],
449 | html input[disabled] {
450 | cursor: default;
451 | }
452 |
453 | /**
454 | * Remove inner padding and border in Firefox 4+.
455 | */
456 |
457 | button::-moz-focus-inner,
458 | input::-moz-focus-inner {
459 | border: 0;
460 | padding: 0;
461 | }
462 |
463 | /**
464 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
465 | * the UA stylesheet.
466 | */
467 |
468 | input {
469 | line-height: normal;
470 | }
471 |
472 | /**
473 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
474 | * 2. Remove excess padding in IE 8/9/10.
475 | * Known issue: excess padding remains in IE 6.
476 | */
477 |
478 | input[type="checkbox"],
479 | input[type="radio"] {
480 | box-sizing: border-box; /* 1 */
481 | padding: 0; /* 2 */
482 | @if $legacy_browser_support {
483 | *height: 13px; /* 3 */
484 | *width: 13px; /* 3 */
485 | }
486 | }
487 |
488 | /**
489 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
490 | * `font-size` values of the `input`, it causes the cursor style of the
491 | * decrement button to change from `default` to `text`.
492 | */
493 |
494 | input[type="number"]::-webkit-inner-spin-button,
495 | input[type="number"]::-webkit-outer-spin-button {
496 | height: auto;
497 | }
498 |
499 | /**
500 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
501 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
502 | * (include `-moz` to future-proof).
503 | */
504 |
505 | input[type="search"] {
506 | -webkit-appearance: textfield; /* 1 */
507 | -moz-box-sizing: content-box;
508 | -webkit-box-sizing: content-box; /* 2 */
509 | box-sizing: content-box;
510 | }
511 |
512 | /**
513 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
514 | * Safari (but not Chrome) clips the cancel button when the search input has
515 | * padding (and `textfield` appearance).
516 | */
517 |
518 | input[type="search"]::-webkit-search-cancel-button,
519 | input[type="search"]::-webkit-search-decoration {
520 | -webkit-appearance: none;
521 | }
522 |
523 | /**
524 | * Define consistent border, margin, and padding.
525 | */
526 |
527 | fieldset {
528 | border: 1px solid #c0c0c0;
529 | margin: 0 2px;
530 | padding: 0.35em 0.625em 0.75em;
531 | }
532 |
533 | /**
534 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
535 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
536 | * 3. Corrects text not wrapping in Firefox 3.
537 | * 4. Corrects alignment displayed oddly in IE 6/7.
538 | */
539 |
540 | legend {
541 | border: 0; /* 1 */
542 | padding: 0; /* 2 */
543 | @if $legacy_browser_support {
544 | white-space: normal; /* 3 */
545 | *margin-left: -7px; /* 4 */
546 | }
547 | }
548 |
549 | /**
550 | * Remove default vertical scrollbar in IE 8/9/10/11.
551 | */
552 |
553 | textarea {
554 | overflow: auto;
555 | }
556 |
557 | /**
558 | * Don't inherit the `font-weight` (applied by a rule above).
559 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
560 | */
561 |
562 | optgroup {
563 | font-weight: bold;
564 | }
565 |
566 | /* Tables
567 | ========================================================================== */
568 |
569 | /**
570 | * Remove most spacing between table cells.
571 | */
572 |
573 | table {
574 | border-collapse: collapse;
575 | border-spacing: 0;
576 | }
577 |
578 | td,
579 | th {
580 | padding: 0;
581 | }
582 |
--------------------------------------------------------------------------------
/docs/html/css/scss/_object.scss:
--------------------------------------------------------------------------------
1 | .section-specification {
2 | table {
3 | width: auto;
4 |
5 | th {
6 | text-align: left;
7 | }
8 | }
9 | }
10 |
11 | .method-title {
12 | margin-left: -15px;
13 | margin-bottom: 8px;
14 | transition: margin-left .3s ease-out;
15 |
16 | .section-method.hide & {
17 | margin-left: 0;
18 | }
19 |
20 | code {
21 | font-weight: 400;
22 | font-size: .85em;
23 | }
24 | }
25 |
26 | .method-info {
27 | background: $object-background;
28 | border-bottom: 1px solid $object-border;
29 | margin: 0 -25px;
30 | padding: 20px 25px 0 25px;
31 | transition: height .3s ease-out;
32 |
33 | position: relative;
34 |
35 | .pointy-thing {
36 | background: $content-background;
37 | height: 10px;
38 | border-bottom: 1px solid $object-border;
39 | margin: -20px -25px 16px -25px;
40 |
41 | &:before {
42 | display: inline-block;
43 | content: "";
44 |
45 | background: $object-background;
46 | border: 1px solid $object-border;
47 | border-bottom: 0;
48 | border-right: 0;
49 |
50 | position: absolute;
51 | left: 21px;
52 | top: 3px;
53 | width: 12px;
54 | height: 12px;
55 | transform: rotate(45deg);
56 | }
57 | }
58 |
59 | .method-subsection {
60 | margin-bottom: 15px;
61 |
62 | .argument-name {
63 | width: 1px;
64 | text-align: right;
65 |
66 | code {
67 | color: #808080;
68 | font-style: italic;
69 | font-weight: 400;
70 | }
71 | }
72 | }
73 | }
74 |
75 | .section-method {
76 | &.hide .method-info {
77 | height: 0 !important;
78 | overflow: hidden;
79 | display: none;
80 | }
81 |
82 | &.hide.animating .method-info {
83 | display: block;
84 | }
85 |
86 | &.animating .method-info {
87 | overflow: hidden;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/docs/html/css/scss/_print.scss:
--------------------------------------------------------------------------------
1 | @media print {
2 | body {
3 | background: #fff;
4 | padding: 8px;
5 | }
6 |
7 | header {
8 | position: static;
9 | background: #fff;
10 | color: #000;
11 | }
12 |
13 | aside {
14 | display: none;
15 | }
16 |
17 | .container {
18 | max-width: none;
19 | padding: 0;
20 | }
21 |
22 | article {
23 | margin-top: 0;
24 |
25 | #content {
26 | border: 0;
27 | background: #fff;
28 | padding: 15px 0 0 0;
29 |
30 | .title {
31 | margin-top: 0;
32 | padding-top: 0;
33 | }
34 | }
35 | }
36 |
37 | .method-info {
38 | &, & .pointy-thing {
39 | background: #fff;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/docs/html/css/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | $body-font: -apple-system-font, "Helvetica Neue", Helvetica, sans-serif;
2 | $code-font: "Source Code Pro", Monaco, Menlo, Consolas, monospace;
3 |
4 | $body-background: #f2f2f2;
5 | $content-background: #fff;
6 | $content-border: #e9e9e9;
7 | $tint-color: #08c;
8 | $object-background: #f9f9f9;
9 | $object-border: #e9e9e9;
10 |
11 | $mobile-max-width: 650px;
12 | $desktop-min-width: 768px;
--------------------------------------------------------------------------------
/docs/html/css/scss/_xcode.scss:
--------------------------------------------------------------------------------
1 | .xcode {
2 | header, aside {
3 | display: none;
4 | }
5 |
6 | .container {
7 | padding: 0;
8 | }
9 |
10 | article {
11 | margin-top: 0;
12 |
13 | #content {
14 | border: 0;
15 | margin: 0;
16 | }
17 | }
18 |
19 | .method-info {
20 | &, .section-method.hide & {
21 | max-height: auto;
22 | overflow: visible;
23 |
24 | &.hiding {
25 | display: block;
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/docs/html/css/scss/style.scss:
--------------------------------------------------------------------------------
1 | @import "variables", "normalize", "layout", "index", "object", "print", "xcode";
2 |
--------------------------------------------------------------------------------
/docs/html/css/style.css:
--------------------------------------------------------------------------------
1 | html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}*{box-sizing:border-box}.clear{clear:both}.clearfix:before,.container:before,article #content:before,article #content footer:before,.clearfix:after,.container:after,article #content:after,article #content footer:after{clear:both;display:table;content:""}.xcode .hide-in-xcode{display:none}body{font:62.5% -apple-system-font,"Helvetica Neue",Helvetica,sans-serif;background:#f2f2f2}@media (max-width: 650px){body{background-color:#fff}}h1,h2,h3{font-weight:300;color:#808080}h1{font-size:2em;color:#000}h4{font-size:13px;line-height:1.5;margin:21px 0 0 0}a{color:#08c;text-decoration:none}pre,code{font-family:"Source Code Pro",Monaco,Menlo,Consolas,monospace;word-wrap:break-word}pre>code,.method-declaration code{display:inline-block;font-size:.85em;padding:4px 0 4px 10px;border-left:5px solid rgba(0,155,51,0.2)}pre>code:before,.method-declaration code:before{content:"Objective-C";display:block;font:9px/1 -apple-system-font,"Helvetica Neue",Helvetica,sans-serif;color:#009b33;text-transform:uppercase;letter-spacing:2px;padding-bottom:6px}pre>code{font-size:inherit}table,th,td{border:1px solid #e9e9e9}table{width:100%}th,td{padding:7px}th>:first-child,td>:first-child{margin-top:0}th>:last-child,td>:last-child{margin-bottom:0}.container{max-width:980px;padding:0 10px;margin:0 auto}@media (max-width: 650px){.container{padding:0}}header{position:fixed;top:0;left:0;width:100%;z-index:2;background:#414141;color:#fff;font-size:1.1em;line-height:25px;letter-spacing:.05em}header #library-title{float:left}header #developer-home{float:right}header h1{font-size:inherit;font-weight:inherit;margin:0}header p{margin:0}header h1,header a{color:inherit}@media (max-width: 650px){header .container{padding:0 10px}}aside{position:fixed;top:25px;left:0;width:100%;height:25px;z-index:2;font-size:1.1em}aside #header-buttons{background:rgba(255,255,255,0.8);margin:0 1px;padding:0;list-style:none;text-align:right;line-height:32px}aside #header-buttons li{display:inline-block;cursor:pointer;padding:0 10px}aside #header-buttons label,aside #header-buttons select{cursor:inherit}aside #header-buttons #on-this-page{position:relative}aside #header-buttons #on-this-page .chevron{display:inline-block;width:14px;height:4px;position:relative}aside #header-buttons #on-this-page .chevron .chevy{background:#878787;height:2px;position:absolute;width:10px}aside #header-buttons #on-this-page .chevron .chevy.chevron-left{left:0;transform:rotateZ(45deg) scale(0.6)}aside #header-buttons #on-this-page .chevron .chevy.chevron-right{right:0;transform:rotateZ(-45deg) scale(0.6)}aside #header-buttons #on-this-page #jump-to{opacity:0;font-size:16px;position:absolute;top:5px;left:0;width:100%;height:100%}article{margin-top:25px}article #content{background:#fff;border:1px solid #e9e9e9;padding:15px 25px 30px 25px;font-size:1.4em;line-height:1.45;position:relative}@media (max-width: 650px){article #content{padding:15px 10px 20px 10px;border:none}}article #content .navigation-top{position:absolute;top:15px;right:25px}article #content .title{margin:21px 0 0 0;padding:15px 0}article #content p{color:#414141;margin:0 0 15px 0}article #content th p:last-child,article #content td p:last-child{margin-bottom:0}article #content main ul{list-style:none;margin-left:24px;margin-bottom:12px;padding:0}article #content main ul li{position:relative;padding-left:1.3em}article #content main ul li:before{content:"\02022";color:#414141;font-size:1.08em;line-height:1;position:absolute;left:0;padding-top:2px}article #content footer .footer-copyright{margin:70px 25px 10px 0}article #content footer p{font-size:.71em;color:#a0a0a0}.index-container{-webkit-flex-direction:column;flex-direction:column}@media (min-width: 768px){.index-container{display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap}}.index-container .index-column{-webkit-flex:1 1 33%;flex:1 1 33%}.section-specification table{width:auto}.section-specification table th{text-align:left}.method-title{margin-left:-15px;margin-bottom:8px;transition:margin-left .3s ease-out}.section-method.hide .method-title{margin-left:0}.method-title code{font-weight:400;font-size:.85em}.method-info{background:#f9f9f9;border-bottom:1px solid #e9e9e9;margin:0 -25px;padding:20px 25px 0 25px;transition:height .3s ease-out;position:relative}.method-info .pointy-thing{background:#fff;height:10px;border-bottom:1px solid #e9e9e9;margin:-20px -25px 16px -25px}.method-info .pointy-thing:before{display:inline-block;content:"";background:#f9f9f9;border:1px solid #e9e9e9;border-bottom:0;border-right:0;position:absolute;left:21px;top:3px;width:12px;height:12px;-webkit-transform:rotate(45deg);transform:rotate(45deg) }.method-info .method-subsection{margin-bottom:15px}.method-info .method-subsection .argument-name{width:1px;text-align:right}.method-info .method-subsection .argument-name code{color:#808080;font-style:italic;font-weight:400}.section-method.hide .method-info{height:0 !important;overflow:hidden;display:none}.section-method.hide.animating .method-info{display:block}.section-method.animating .method-info{overflow:hidden}@media print{body{background:#fff;padding:8px}header{position:static;background:#fff;color:#000}aside{display:none}.container{max-width:none;padding:0}article{margin-top:0}article #content{border:0;background:#fff;padding:15px 0 0 0}article #content .title{margin-top:0;padding-top:0}.method-info,.method-info .pointy-thing{background:#fff}}.xcode header,.xcode aside{display:none}.xcode .container{padding:0}.xcode article{margin-top:0}.xcode article #content{border:0;margin:0}.xcode .method-info,.section-method.hide .xcode .method-info{max-height:auto;overflow:visible}.xcode .method-info.hiding,.section-method.hide .xcode .method-info.hiding{display:block}
2 |
3 |
--------------------------------------------------------------------------------
/docs/html/hierarchy.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KSYAirStreamer_iOS Hierarchy
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Kingsoft
22 |
23 |
24 |
25 |
26 |
27 |
36 |
37 |
38 |
39 |
40 |
41 | KSYAirStreamer_iOS Hierarchy
42 |
43 |
44 |
45 |
Class Hierarchy
46 |
47 |
48 |
49 | - KSYAUAudioCapture
50 |
55 |
56 |
57 | - NSObject
58 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
Protocol References
78 |
83 |
84 |
85 |
Constant References
86 |
95 |
96 |
97 |
98 |
99 |
100 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/docs/html/img/button_bar_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/docs/html/img/button_bar_background.png
--------------------------------------------------------------------------------
/docs/html/img/disclosure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/docs/html/img/disclosure.png
--------------------------------------------------------------------------------
/docs/html/img/disclosure_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/docs/html/img/disclosure_open.png
--------------------------------------------------------------------------------
/docs/html/img/library_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/docs/html/img/library_background.png
--------------------------------------------------------------------------------
/docs/html/img/title_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/docs/html/img/title_background.png
--------------------------------------------------------------------------------
/docs/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | KSYAirStreamer_iOS Reference
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | Kingsoft
22 |
23 |
24 |
25 |
26 |
27 |
36 |
37 |
38 |
39 |
40 |
41 | KSYAirStreamer_iOS Reference
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
Class References
51 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
Protocol References
69 |
74 |
75 |
76 |
77 |
Constant References
78 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/docs/html/js/script.js:
--------------------------------------------------------------------------------
1 | function $() {
2 | return document.querySelector.apply(document, arguments);
3 | }
4 |
5 | if (navigator.userAgent.indexOf("Xcode") != -1) {
6 | document.documentElement.classList.add("xcode");
7 | }
8 |
9 | var jumpTo = $("#jump-to");
10 |
11 | if (jumpTo) {
12 | jumpTo.addEventListener("change", function(e) {
13 | location.hash = this.options[this.selectedIndex].value;
14 | });
15 | }
16 |
17 | function hashChanged() {
18 | if (/^#\/\/api\//.test(location.hash)) {
19 | var element = document.querySelector("a[name='" + location.hash.substring(1) + "']");
20 |
21 | if (!element) {
22 | return;
23 | }
24 |
25 | element = element.parentNode;
26 |
27 | element.classList.remove("hide");
28 | fixScrollPosition(element);
29 | }
30 | }
31 |
32 | function fixScrollPosition(element) {
33 | var scrollTop = element.offsetTop - 150;
34 | document.documentElement.scrollTop = scrollTop;
35 | document.body.scrollTop = scrollTop;
36 | }
37 |
38 | [].forEach.call(document.querySelectorAll(".section-method"), function(element) {
39 | element.classList.add("hide");
40 |
41 | element.querySelector(".method-title a").addEventListener("click", function(e) {
42 | var info = element.querySelector(".method-info"),
43 | infoContainer = element.querySelector(".method-info-container");
44 |
45 | element.classList.add("animating");
46 | info.style.height = (infoContainer.clientHeight + 40) + "px";
47 | fixScrollPosition(element);
48 | element.classList.toggle("hide");
49 | if (element.classList.contains("hide")) {
50 | e.preventDefault();
51 | }
52 | setTimeout(function() {
53 | element.classList.remove("animating");
54 | }, 300);
55 | });
56 | });
57 |
58 | window.addEventListener("hashchange", hashChanged);
59 | hashChanged();
60 |
--------------------------------------------------------------------------------
/prebuilt/include/KSYAirStreamer/KSYAirTunesServer.h:
--------------------------------------------------------------------------------
1 | //
2 | // KSYAirTunesServer.h
3 | // KSYAirStreamer
4 | //
5 | // Created by yiqian on 11/04/2017.
6 | // Copyright © 2017 pengbins. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 | /** airplay的错误码Domain */
12 | FOUNDATION_EXTERN NSString *const KSYAirErrorDomain NS_AVAILABLE_IOS(7_0);
13 | /** airplay的错误码 */
14 | typedef NS_ENUM(NSUInteger, KSYAirErrorCode) {
15 | /// AirPlay端口冲突
16 | KSYAirErrorCodePortConflict,
17 | /// 网络未连接
18 | KSYAirErrorCodeNetworkDisconnection,
19 | /// 连接AirPlay超时
20 | KSYAirErrorCodeAirPlaySelectTimeout,
21 | /// 连接失败
22 | KSYAirErrorCodeConnectFailed,
23 | /// 连接断开
24 | KSYAirErrorCodeConnectBreak,
25 | /// 其他错误
26 | KSYAirErrorCodeOther
27 | };
28 |
29 | /** airplay的状态信息 */
30 | typedef NS_ENUM(NSUInteger, KSYAirState) {
31 | /// 初始状态, 空闲
32 | KSYAirState_Idle,
33 | /// 连接中
34 | KSYAirState_Connecting,
35 | /// 镜像中, 连接完成了
36 | KSYAirState_Mirroring,
37 | /// 断开连接中
38 | KSYAirState_Disconnecting,
39 | /// 发生错误了
40 | KSYAirState_Error,
41 | };
42 |
43 | /** airplay的状态信息 */
44 | typedef NS_ENUM(NSUInteger, KSYAirVideoDecoder) {
45 | /// 软解, 解码后画面通过videoProcessingCallback回调给出
46 | KSYAirVideoDecoder_SOFTWARE,
47 | /// 硬解, 解码后画面通过videoProcessingCallback回调给出
48 | KSYAirVideoDecoder_VIDEOTOOLBOX,
49 | /// 无解码,将接收到的264码流直接通过videoBitStreamCallback回调函数给出
50 | KSYAirVideoDecoder_NONE = 0x1000,
51 | };
52 |
53 |
54 | #pragma mark - KSYAirTunesConfig
55 | /** airplay的配置信息 */
56 | @interface KSYAirTunesConfig : NSObject
57 | /// AirPlay 设备的名字
58 | @property(nonatomic, copy) NSString *airplayName;
59 | /// 接收设备的尺寸 (默认为 960x960)
60 | /// 如果videoSize的宽高相同, 则横竖屏旋转时,输出的分辨率保持不变;
61 | /// 当宽高不同时, 横竖屏旋转后, 高度保持不变, 宽度会跟随设备的屏幕比例变化
62 | /// 请注意,宽高比需要和屏幕的比例相同, 否则内部自动按照画面小的数值计算
63 | @property(nonatomic, assign) CGSize videoSize;
64 | /// 是否需要在宽高不同时, 在屏幕两边填上黑边 (默认为 NO)
65 | @property(nonatomic, assign) BOOL padding;
66 | /// 希望接收到ios发送端的视频帧率 (有效值为 10, 15, 30), 默认为30
67 | @property(nonatomic, assign) int framerate;
68 | /// 设置airtunes 服务的监听端口, 0 表示系统自动分配
69 | @property(nonatomic, assign) short airTunesPort;
70 | /// 设置视频数据的接收端口,默认是7100, 当7100被占用时, 会尝试+1 尝试10次, 如果仍然失败报告端口冲突
71 | @property(nonatomic, assign) short airVideoPort;
72 | /// 设备的mac地址, 默认随机生成,(长度为6字节)
73 | @property(nonatomic, copy) NSData *macAddr;
74 | /// AirPlay接收数据的解码器(默认为KSYAirVideoDecoder_SOFTWARE)
75 | @property(nonatomic, assign) KSYAirVideoDecoder videoDecoder;
76 | @end
77 |
78 |
79 | #pragma mark - KSYAirDelegate
80 | @class KSYAirTunesServer;
81 |
82 | /**
83 | airplay 镜像状态变化的代理
84 | */
85 | @protocol KSYAirDelegate
86 | @required
87 | /**
88 | airplay 镜像成功开始了
89 |
90 | @param server airplay服务对象
91 | */
92 | - (void)didStartMirroring:(KSYAirTunesServer *)server;
93 |
94 | @required
95 | /**
96 | airplay 镜像 遇到错误了
97 |
98 | @param server airplay服务对象
99 | @param error 遇到的错误, code 参见 KSYAirErrorCode的定义
100 | */
101 | - (void)mirroringErrorDidOcccur:(KSYAirTunesServer *)server withError:(NSError *)error;
102 |
103 | @required
104 | /**
105 | airplay 镜像成功结束了
106 |
107 | @param server airplay服务对象
108 | */
109 | - (void)didStopMirroring:(KSYAirTunesServer *)server;
110 |
111 | @end
112 |
113 | #pragma mark - KSYAirTunesServer
114 |
115 | /**
116 | Airplay 接收server
117 | */
118 | @interface KSYAirTunesServer : NSObject
119 |
120 | /**
121 | 带鉴权的构造接收server实例
122 |
123 | @param token 鉴权信息, 请联系商务获取
124 | @param error 鉴权过程中的错误信息
125 | @return 新构造的实例
126 | */
127 | - (instancetype) initWithToken:(NSString*) token
128 | error:(NSError**) error;
129 |
130 | /**
131 | SDK 过期时间
132 | */
133 | @property(nonatomic, readonly) NSDate* expireDate;
134 |
135 | /**
136 | 获取屏幕画面的回调
137 | */
138 | @property(nonatomic, copy) void(^videoProcessingCallback)(CVPixelBufferRef pixelBuffer, CMTime timeInfo );
139 |
140 | /**
141 | 获取屏幕码流的回调
142 | */
143 | @property(nonatomic, copy) void(^videoBitStreamCallback)(NSData* data, BOOL bParameterSet, CMTime timeInfo);
144 |
145 | /**
146 | 录制过程的通知代理
147 | */
148 | @property(nonatomic, weak) id delegate;
149 |
150 | /**
151 | airplay 录制状态
152 | */
153 | @property(nonatomic, readonly) KSYAirState airState;
154 |
155 | /**
156 | 启动服务
157 | @param cfg 服务的配置信息
158 | */
159 | - (void) startServerWithCfg:(KSYAirTunesConfig*)cfg;
160 |
161 | /**
162 | 停止服务
163 | */
164 | - (void) stopServer;
165 |
166 | /**
167 | 查询 errorcode 的名称
168 |
169 | @param errCode 错误码
170 | @return 错误码的名称字符串
171 | */
172 | - (NSString*) getKSYAirErrorName : (KSYAirErrorCode) errCode;
173 |
174 | @end
175 |
176 | /**
177 | 解析264的参数集
178 |
179 | @param data airplay原始的 参数集(sps, pps)数据
180 | @param psData 解析后的参数集数据, 请保证传入的是指针数组, 且每个元素都为NULL
181 | @param psSize 解析后的每个参数集的长度
182 | @param psCnt 解析出来的参数集的个数
183 | @return nallength的字节数
184 | */
185 | int KSYAirParseParamSets(NSData* data, uint8_t* psData[], size_t psSize[], size_t * psCnt);
186 |
187 | #define KSYAIRSTREAMER_VER 1.5.0
188 | #define KSYAIRSTREAMER_ID 9d3b0f365aa8f461d9f1f75db308ca8551f3c92c
189 |
--------------------------------------------------------------------------------
/prebuilt/libs/libksyairserver.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ksvc/KSYAirStreamer_iOS/260384cf4d7385e5703184c3a8ed42b21057e03a/prebuilt/libs/libksyairserver.a
--------------------------------------------------------------------------------
/source/KSYAirStreamKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // KSYViewController.h
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 |
9 |
10 | #import
11 | #import
12 | #import "KSYAirTunesServer.h"
13 |
14 | /**
15 | 音频采集模块, 主要是重写 isHeadsetPluggedIn方法, 避免音频采集的问题
16 | */
17 | @interface KSYAudioCap : KSYAUAudioCapture
18 | @end
19 |
20 | /**
21 | airplay 镜像录屏 + KSYStreamerBase 推流
22 | */
23 | @interface KSYAirStreamKit : NSObject
24 |
25 | /**
26 | 带鉴权的构造接收server实例
27 |
28 | @param token 鉴权信息, 请联系商务获取
29 | @param error 鉴权过程中的错误信息
30 | @return 新构造的实例
31 | */
32 | - (instancetype) initWithToken:(NSString*) token
33 | error:(NSError**) error;
34 |
35 | /** airplay 接受端 */
36 | @property KSYAirTunesServer *airTunesServer;
37 | /** airplay 配置信息 */
38 | @property KSYAirTunesConfig *airCfg;
39 | /** 麦克风采集设备 */
40 | @property KSYAudioCap *aCapDev;
41 | /** 音频mixer (音频buffer) */
42 | @property KSYAudioMixer *aMixer;
43 | /** rtmp 推流地址 */
44 | @property NSString * streamUrl;
45 | /** rtmp 推流视频码率 */
46 | @property int videoBitrate;
47 | /** rtmp 推流 */
48 | @property KSYStreamerBase *streamerBase;
49 | /** 录制过程的代理 */
50 | @property(nonatomic, weak) id delegate;
51 |
52 | /**
53 | 启动镜像服务, 当成功建立airplay镜像连接时, 再启动音频采集和推流
54 | */
55 | - (void) startService;
56 |
57 | /**
58 | 停止镜像服务
59 | */
60 | - (void) stopService;
61 |
62 | @end
63 |
--------------------------------------------------------------------------------
/source/KSYAirStreamKit.m:
--------------------------------------------------------------------------------
1 | //
2 | // KSYViewController.m
3 | // KSYAirStreamer
4 | //
5 | // Created by pengbins on 04/11/2017.
6 | // Copyright (c) 2017 pengbins. All rights reserved.
7 | //
8 | #import "KSYAirStreamKit.h"
9 |
10 | @implementation KSYAudioCap
11 | + (BOOL) isHeadsetPluggedIn {
12 | return NO;
13 | }
14 |
15 | @end
16 |
17 |
18 | @interface KSYAirStreamKit () {
19 | int _autoRetryCnt;
20 | BOOL _bRetry;
21 | int _maxAutoRetry;
22 | int _codecOpenFailCnt;
23 | }
24 |
25 | @end
26 |
27 | @implementation KSYAirStreamKit
28 |
29 |
30 | - (id) init {
31 | return [self initWithToken:nil error:nil];
32 | }
33 | /**
34 | 带鉴权的构造接收server实例
35 |
36 | @param token 鉴权信息, 请联系商务获取
37 | @param error 鉴权过程中的错误信息
38 | @return 新构造的实例
39 | */
40 | - (instancetype) initWithToken:(NSString*) token
41 | error:(NSError**) error {
42 | self = [super init];
43 | if (self == nil) {
44 | return nil;
45 | }
46 | NSError * outErr = nil;
47 | _airTunesServer = [[KSYAirTunesServer alloc] initWithToken:token error:&outErr];
48 | if (outErr) {
49 | if(error){
50 | *error = outErr;
51 | }
52 | self = nil;
53 | return nil;
54 | }
55 | _streamerBase = [[KSYStreamerBase alloc] init];
56 | _aMixer = [[KSYAudioMixer alloc] init];
57 | _aMixer.mainTrack = 0;
58 | [_aMixer setTrack:0 enable:YES];
59 | _airTunesServer.delegate = self;
60 | __weak typeof(self) weakSelf = self;
61 | _airTunesServer.videoProcessingCallback = ^(CVPixelBufferRef pixelBuffer, CMTime timeInfo) {
62 | [weakSelf.streamerBase processVideoPixelBuffer:pixelBuffer timeInfo:timeInfo];
63 | };
64 | _aMixer.pcmProcessingCallback = ^(uint8_t **pData, int nbSample, CMTime pts) {
65 | [weakSelf.streamerBase processAudioData:pData
66 | nbSample:nbSample
67 | withFormat:weakSelf.aMixer.outFmtDes
68 | timeinfo:&pts];
69 | };
70 | _streamerBase.videoCodec = KSYVideoCodec_AUTO;
71 | _streamerBase.videoEncodePerf = KSYVideoEncodePer_HighPerformance;
72 | _streamerBase.audioCodec = KSYAudioCodec_AT_AAC;
73 | _streamerBase.audiokBPS = 64;
74 | _streamerBase.streamStateChange = ^(KSYStreamState state) {
75 | [weakSelf onStreamStateChange:state];
76 | };
77 |
78 | _autoRetryCnt = 0;
79 | _maxAutoRetry = 5;
80 | _bRetry = NO;
81 | _codecOpenFailCnt=0;
82 | NSNotificationCenter* dc = [NSNotificationCenter defaultCenter];
83 | [dc addObserver:self
84 | selector:@selector(onNetStateEvent)
85 | name:KSYNetStateEventNotification
86 | object:nil];
87 | return self;
88 | }
89 | - (void) dealloc {
90 | [[NSNotificationCenter defaultCenter] removeObserver:self];
91 | }
92 |
93 | - (void) startService {
94 | [_airTunesServer startServerWithCfg:_airCfg];
95 | }
96 | - (void) stopService {
97 | [_streamerBase stopStream];
98 | [_airTunesServer stopServer];
99 | }
100 |
101 | #pragma mark - KSYAirDelegate
102 | - (void) didStartMirroring:(KSYAirTunesServer *)server {
103 | _streamerBase.videoFPS = _airCfg.framerate;
104 | _streamerBase.videoMaxBitrate = _videoBitrate;
105 | _streamerBase.videoInitBitrate = _videoBitrate*6/10;
106 | _streamerBase.videoMinBitrate = 0;
107 | [_streamerBase startStream:[NSURL URLWithString:_streamUrl]];
108 | if (_delegate && [_delegate respondsToSelector:@selector(didStartMirroring:)]) {
109 | dispatch_async(dispatch_get_main_queue(), ^() {
110 | [_delegate didStartMirroring:server];
111 | });
112 | }
113 | __weak typeof(self) weakSelf = self;
114 | _aCapDev = [[KSYAudioCap alloc] init];
115 | // 关闭降噪处理, 减少CPU占用
116 | _aCapDev.noiseSuppressionLevel = KSYAudioNoiseSuppress_OFF;
117 | _aCapDev.pcmProcessingCallback = ^(uint8_t **pData, int len, const AudioStreamBasicDescription *fmt, CMTime timeInfo) {
118 | [weakSelf.aMixer processAudioData:pData
119 | nbSample:len
120 | withFormat:fmt
121 | timeinfo:timeInfo
122 | of:0];
123 | };
124 | [_aCapDev startCapture];
125 | }
126 | - (void)mirroringErrorDidOcccur:(KSYAirTunesServer *)server withError:(NSError *)error {
127 | if (_delegate && [_delegate respondsToSelector:@selector(mirroringErrorDidOcccur:withError:)]) {
128 | dispatch_async(dispatch_get_main_queue(), ^() {
129 | [_delegate mirroringErrorDidOcccur:server withError:error];
130 | });
131 | }
132 | }
133 | - (void)didStopMirroring:(KSYAirTunesServer *)server {
134 | [_streamerBase stopStream];
135 | [_aCapDev stopCapture];
136 | _aCapDev = nil;
137 | if (_delegate && [_delegate respondsToSelector:@selector(didStartMirroring:)]) {
138 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
139 | [_delegate didStopMirroring:server ];
140 | });
141 | }
142 | }
143 |
144 | #pragma mark - stream state
145 |
146 | - (void) onStreamStateChange: (KSYStreamState) state {
147 | if (!_streamerBase){
148 | return;
149 | }
150 | if (state == KSYStreamStateError){
151 | [self onStreamError:_streamerBase.streamErrorCode];
152 | }
153 | else if (state == KSYStreamStateConnected){
154 | _autoRetryCnt = _maxAutoRetry;
155 | _bRetry = NO;
156 | _codecOpenFailCnt = 0;
157 | }
158 | }
159 | - (void) onNetStateEvent {
160 | KSYNetStateCode code = [_streamerBase netStateCode];
161 | if (code == KSYNetStateCode_REACHABLE) {
162 | if ( _streamerBase.streamState == KSYStreamStateError) {
163 | [self tryRtmpReconnect:1];
164 | }
165 | }
166 | }
167 |
168 | - (void) onStreamError: (KSYStreamErrorCode) errCode {
169 | NSString * name = [_streamerBase getCurKSYStreamErrorCodeName];
170 | NSLog(@"stream Error: %@", [name substringFromIndex:19]);
171 | if (errCode == KSYStreamErrorCode_CONNECT_BREAK ||
172 | errCode == KSYStreamErrorCode_AV_SYNC_ERROR ||
173 | errCode == KSYStreamErrorCode_Connect_Server_failed ||
174 | errCode == KSYStreamErrorCode_DNS_Parse_failed) {
175 | if (_bRetry == NO){
176 | [self tryRtmpReconnect:2];
177 | }
178 | }
179 | else if (errCode == KSYStreamErrorCode_RTMP_Publish_failed ||
180 | errCode == KSYStreamErrorCode_RTMP_AlreadyExistStreamName||
181 | errCode == KSYStreamErrorCode_RTMP_InternalError){
182 | if (_bRetry == NO){
183 | [self tryRtmpReconnect:5];
184 | }
185 | }
186 | else if (errCode == KSYStreamErrorCode_CODEC_OPEN_FAILED) {
187 | if (_codecOpenFailCnt>3) {// 硬编码打开失败连续3次之后,切到软编码
188 | _streamerBase.videoCodec = KSYVideoCodec_X264;
189 | }
190 | _codecOpenFailCnt++;
191 | _autoRetryCnt = _maxAutoRetry;
192 | if (_bRetry == NO){
193 | [self tryRtmpReconnect:1];
194 | }
195 | }
196 | // 其他一些错误码跟地址有关, 需要重新获取地址再重连, 否则不起作用
197 | }
198 |
199 | - (void) tryRtmpReconnect:(double) delay {
200 | _bRetry = YES;
201 | int64_t delaySec = (int64_t)(delay * NSEC_PER_SEC);
202 | dispatch_time_t delayT = dispatch_time(DISPATCH_TIME_NOW, delaySec);
203 | dispatch_after(delayT, dispatch_get_main_queue(), ^{
204 | _bRetry = NO;
205 | if (_autoRetryCnt <= 0 || _streamerBase.netReachState == KSYNetReachState_Bad) {
206 | return;
207 | }
208 | if (!_streamerBase.isStreaming) {
209 | NSLog(@"retry connect %d/%d", _autoRetryCnt, _maxAutoRetry);
210 | _autoRetryCnt--;
211 | [_streamerBase startStream:_streamerBase.hostURL];
212 | }
213 | });
214 | }
215 | @end
216 |
--------------------------------------------------------------------------------