├── .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 | [![Apps Using](https://img.shields.io/cocoapods/at/KSYAirStreamer.svg?label=Apps%20Using%20KSYAirStreamer&colorB=28B9FE)](http://cocoapods.org/pods/KSYAirStreamer)[![Downloads](https://img.shields.io/cocoapods/dt/KSYAirStreamer.svg?label=Total%20Downloads%20KSYAirStreamer&colorB=28B9FE)](http://cocoapods.org/pods/KSYAirStreamer) 4 | 5 | 6 | [![Build Status](https://travis-ci.org/ksvc/KSYAirStreamer_iOS.svg?branch=master)](https://travis-ci.org/ksvc/KSYAirStreamer_iOS) 7 | [![CocoaPods platform](https://img.shields.io/cocoapods/p/KSYAirStreamer.svg)](https://cocoapods.org/pods/KSYAirStreamer) 8 | [![CocoaPods version](https://img.shields.io/cocoapods/v/KSYAirStreamer.svg?label=pod_github)](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 |

17 | KSYAirStreamer_iOS 18 |

19 | 20 |

21 | Kingsoft 22 |

23 | 24 |
25 |
26 | 27 | 99 | 100 |
101 |
102 |
103 |
104 |

KSYAirStreamKit Class Reference

105 | 106 | 107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
Inherits fromNSObject
Declared inKSYAirStreamKit.h
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 |

– initWithToken:error: 141 |

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 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 |
token

鉴权信息, 请联系商务获取

error

鉴权过程中的错误信息

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 |

  airTunesServer 204 |

205 | 206 |
207 |
208 | 209 |
210 | 211 | 212 |
213 |

airplay 接受端

214 |
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 |

  airCfg 245 |

246 | 247 |
248 |
249 | 250 |
251 | 252 | 253 |
254 |

airplay 配置信息

255 |
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 |

  aCapDev 286 |

287 | 288 |
289 |
290 | 291 |
292 | 293 | 294 |
295 |

麦克风采集设备

296 |
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 |

  aMixer 327 |

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 |

  streamUrl 368 |

369 | 370 |
371 |
372 | 373 |
374 | 375 | 376 |
377 |

rtmp 推流地址

378 |
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 |

  videoBitrate 409 |

410 | 411 |
412 |
413 | 414 |
415 | 416 | 417 |
418 |

rtmp 推流视频码率

419 |
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 |

  streamerBase 450 |

451 | 452 |
453 |
454 | 455 |
456 | 457 | 458 |
459 |

rtmp 推流

460 |
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 |

  delegate 491 |

492 | 493 |
494 |
495 | 496 |
497 | 498 | 499 |
500 |

录制过程的代理

501 |
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 |

– startService 532 |

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 |

– stopService 573 |

574 | 575 |
576 |
577 | 578 |
579 | 580 | 581 |
582 |

停止镜像服务

583 |
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 |
624 | 632 |
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 |

17 | KSYAirStreamer_iOS 18 |

19 | 20 |

21 | Kingsoft 22 |

23 | 24 |
25 |
26 | 27 | 89 | 90 |
91 |
92 |
93 |
94 |

KSYAirTunesConfig Class Reference

95 | 96 | 97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
Inherits fromNSObject
Declared inKSYAirTunesServer.h
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 |

  airplayName 131 |

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 |

  videoSize 172 |

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 |

  padding 216 |

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 |

  framerate 257 |

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 |

  airTunesPort 298 |

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 |

  airVideoPort 339 |

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 |

  macAddr 380 |

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 |

  videoDecoder 421 |

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 |
472 | 480 |
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 |

17 | KSYAirStreamer_iOS 18 |

19 | 20 |

21 | Kingsoft 22 |

23 | 24 |
25 |
26 | 27 | 95 | 96 |
97 |
98 |
99 |
100 |

KSYAirTunesServer Class Reference

101 | 102 | 103 |
104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 |
Inherits fromNSObject
Declared inKSYAirTunesServer.h
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 |

– initWithToken:error: 137 |

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 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 |
token

鉴权信息, 请联系商务获取

error

鉴权过程中的错误信息

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 |

  expireDate 200 |

201 | 202 |
203 |
204 | 205 |
206 | 207 | 208 |
209 |

SDK 过期时间

210 |
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 |

  videoProcessingCallback 241 |

242 | 243 |
244 |
245 | 246 |
247 | 248 | 249 |
250 |

获取屏幕画面的回调

251 |
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 |

  videoBitStreamCallback 282 |

283 | 284 |
285 |
286 | 287 |
288 | 289 | 290 |
291 |

获取屏幕码流的回调

292 |
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 |

  delegate 323 |

324 | 325 |
326 |
327 | 328 |
329 | 330 | 331 |
332 |

录制过程的通知代理

333 |
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 |

  airState 364 |

365 | 366 |
367 |
368 | 369 |
370 | 371 | 372 |
373 |

airplay 录制状态

374 |
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 |

– startServerWithCfg: 405 |

406 | 407 |
408 |
409 | 410 |
411 | 412 | 413 |
414 |

启动服务

415 |
416 | 417 | 418 | 419 |
- (void)startServerWithCfg:(KSYAirTunesConfig *)cfg
420 | 421 | 422 | 423 |
424 |

Parameters

425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 |
cfg

服务的配置信息

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 |

– stopServer 458 |

459 | 460 |
461 |
462 | 463 |
464 | 465 | 466 |
467 |

停止服务

468 |
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 |

– getKSYAirErrorName: 499 |

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 | 523 | 524 | 525 | 526 |
errCode

错误码

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 |
567 | 575 |
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 |

17 | KSYAirStreamer_iOS 18 |

19 | 20 |

21 | Kingsoft 22 |

23 | 24 |
25 |
26 | 27 | 67 | 68 |
69 |
70 |
71 |
72 |

KSYAudioCap Class Reference

73 | 74 | 75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
Inherits fromKSYAUAudioCapture
Declared inKSYAirStreamKit.h
84 | 85 | 86 | 87 | 88 |
89 | 90 |

Overview

91 |

音频采集模块, 主要是重写 isHeadsetPluggedIn方法, 避免音频采集的问题

92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
104 | 105 |
106 | 114 |
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 |

17 | KSYAirStreamer_iOS 18 |

19 | 20 |

21 | Kingsoft 22 |

23 | 24 |
25 |
26 | 27 | 57 | 58 |
59 |
60 |
61 |
62 |

KSYAirErrorCode Constants Reference

63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 |
Declared inKSYAirTunesServer.h
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |

KSYAirErrorCode

79 | 80 | 81 |
82 |

airplay的错误码

83 |
84 | 85 | 86 |
87 | 88 | 89 |

Definition

90 | typedef NS_ENUM(NSUInteger, KSYAirErrorCode ) {
91 | 92 |    KSYAirErrorCodePortConflict,
93 | 94 |    KSYAirErrorCodeNetworkDisconnection,
95 | 96 |    KSYAirErrorCodeAirPlaySelectTimeout,
97 | 98 |    KSYAirErrorCodeConnectFailed,
99 | 100 |    KSYAirErrorCodeConnectBreak,
101 | 102 |    KSYAirErrorCodeOther,
103 | 104 | };
105 | 106 |
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 |
236 | 244 |
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 |

17 | KSYAirStreamer_iOS 18 |

19 | 20 |

21 | Kingsoft 22 |

23 | 24 |
25 |
26 | 27 | 57 | 58 |
59 |
60 |
61 |
62 |

KSYAirState Constants Reference

63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 |
Declared inKSYAirTunesServer.h
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |

KSYAirState

79 | 80 | 81 |
82 |

airplay的状态信息

83 |
84 | 85 | 86 |
87 | 88 | 89 |

Definition

90 | typedef NS_ENUM(NSUInteger, KSYAirState ) {
91 | 92 |    KSYAirState_Idle,
93 | 94 |    KSYAirState_Connecting,
95 | 96 |    KSYAirState_Mirroring,
97 | 98 |    KSYAirState_Disconnecting,
99 | 100 |    KSYAirState_Error,
101 | 102 | };
103 | 104 |
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 |
217 | 225 |
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 |

17 | KSYAirStreamer_iOS 18 |

19 | 20 |

21 | Kingsoft 22 |

23 | 24 |
25 |
26 | 27 | 57 | 58 |
59 |
60 |
61 |
62 |

KSYAirVideoDecoder Constants Reference

63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 |
Declared inKSYAirTunesServer.h
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |

KSYAirVideoDecoder

79 | 80 | 81 |
82 |

airplay的状态信息

83 |
84 | 85 | 86 |
87 | 88 | 89 |

Definition

90 | typedef NS_ENUM(NSUInteger, KSYAirVideoDecoder ) {
91 | 92 |    KSYAirVideoDecoder_SOFTWARE,
93 | 94 |    KSYAirVideoDecoder_VIDEOTOOLBOX,
95 | 96 |    KSYAirVideoDecoder_NONE = 0 x1000,
97 | 98 | };
99 | 100 |
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 |
179 | 187 |
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 |

17 | KSYAirStreamer_iOS 18 |

19 | 20 |

21 | Kingsoft 22 |

23 | 24 |
25 |
26 | 27 | 79 | 80 |
81 |
82 |
83 |
84 |

KSYAirDelegate Protocol Reference

85 | 86 | 87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
Conforms toNSObject
Declared inKSYAirTunesServer.h
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 |

– didStartMirroring: 121 | required method

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 | 145 | 146 | 147 | 148 |
server

airplay服务对象

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 |

– mirroringErrorDidOcccur:withError: 174 | required method

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 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 |
server

airplay服务对象

error

遇到的错误, code 参见 KSYAirErrorCode的定义

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 |

– didStopMirroring: 232 | required method

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 | 256 | 257 | 258 | 259 |
server

airplay服务对象

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 |
295 | 303 |
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 |

17 | KSYAirStreamer_iOS 18 |

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 | 70 | 71 |
72 | 73 | 74 | 75 |
76 | 77 |

Protocol References

78 | 83 | 84 | 85 |

Constant References

86 | 95 | 96 | 97 |
98 | 99 | 100 |
101 | 109 |
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 |

17 | KSYAirStreamer_iOS 18 |

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 |
95 | 103 |
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 | --------------------------------------------------------------------------------