CoolFlutterIJK.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. //
  2. // Created by Caijinglong on 2019-03-08.
  3. //
  4. #import "CoolFlutterIJK.h"
  5. #import "CoolVideoInfo.h"
  6. #import "CoolFlutterResult.h"
  7. #import <AVFoundation/AVFoundation.h>
  8. #import <libkern/OSAtomic.h>
  9. @interface CoolFlutterIJK () <FlutterTexture, KKIjkNotifyDelegate>
  10. @end
  11. @implementation CoolFlutterIJK {
  12. int64_t textureId;
  13. CADisplayLink *displayLink;
  14. NSObject <FlutterTextureRegistry> *textures;
  15. IJKFFMoviePlayerController *controller;
  16. CVPixelBufferRef latestPixelBuffer;
  17. FlutterMethodChannel *channel;
  18. CoolIjkNotifyChannel *notifyChannel;
  19. int degree;
  20. CoolFlutterResult *prepareResult;
  21. }
  22. - (instancetype)initWithRegistrar:(NSObject <FlutterPluginRegistrar> *)registrar {
  23. self = [super init];
  24. if (self) {
  25. self.registrar = registrar;
  26. textures = [self.registrar textures];
  27. textureId = [textures registerTexture:self];
  28. NSString *channelName = [NSString stringWithFormat:@"top.kikt/ijkplayer/%lli", textureId];
  29. channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:[registrar messenger]];
  30. __weak typeof(&*self) weakSelf = self;
  31. [channel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
  32. [weakSelf handleMethodCall:call result:result];
  33. }];
  34. self.isDisposed = NO;
  35. }
  36. return self;
  37. }
  38. - (void)dispose {
  39. if(self.isDisposed){
  40. return;
  41. }
  42. self.isDisposed = YES;
  43. [notifyChannel dispose];
  44. [[self.registrar textures] unregisterTexture:self.id];
  45. [controller stop];
  46. [controller shutdown];
  47. controller = nil;
  48. displayLink.paused = YES;
  49. [displayLink invalidate];
  50. }
  51. - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
  52. if(self.isDisposed){
  53. return;
  54. }
  55. if ([@"play" isEqualToString:call.method]) {
  56. [self play];
  57. result(@(YES));
  58. } else if ([@"pause" isEqualToString:call.method]) {
  59. [self pause];
  60. result(@(YES));
  61. } else if ([@"stop" isEqualToString:call.method]) {
  62. [self stop];
  63. result(@(YES));
  64. } else if ([@"setNetworkDataSource" isEqualToString:call.method]) {
  65. @try {
  66. NSDictionary *params = call.arguments;
  67. NSString *uri = params[@"uri"];
  68. NSDictionary *headers = params[@"headers"];
  69. [self setDataSourceWithUri:uri headers:headers result:[CoolFlutterResult resultWithResult:result]];
  70. }
  71. @catch (NSException *exception) {
  72. NSLog(@"Exception occurred: %@, %@", exception, [exception userInfo]);
  73. result([FlutterError errorWithCode:@"1" message:@"设置失败" details:nil]);
  74. }
  75. } else if ([@"setAssetDataSource" isEqualToString:call.method]) {
  76. @try {
  77. NSDictionary *params = [call arguments];
  78. NSString *name = params[@"name"];
  79. NSString *pkg = params[@"package"];
  80. IJKFFMoviePlayerController *playerController = [self createControllerWithAssetName:name pkg:pkg];
  81. [self setDataSourceWithController:playerController result:[CoolFlutterResult resultWithResult:result]];
  82. }
  83. @catch (NSException *exception) {
  84. NSLog(@"Exception occurred: %@, %@", exception, [exception userInfo]);
  85. result([FlutterError errorWithCode:@"1" message:@"设置失败" details:nil]);
  86. }
  87. } else if ([@"setFileDataSource" isEqualToString:call.method]) {
  88. NSDictionary *params = call.arguments;
  89. NSString *path = params[@"path"];
  90. IJKFFMoviePlayerController *playerController = [self createControllerWithPath:path];
  91. [self setDataSourceWithController:playerController result:[CoolFlutterResult resultWithResult:result]];
  92. } else if ([@"seekTo" isEqualToString:call.method]) {
  93. NSDictionary *params = call.arguments;
  94. double target = [params[@"target"] doubleValue];
  95. [self seekTo:target];
  96. result(@(YES));
  97. } else if ([@"getInfo" isEqualToString:call.method]) {
  98. CoolVideoInfo *info = [self getInfo];
  99. result([info toMap]);
  100. } else if ([@"setVolume" isEqualToString:call.method]) {
  101. NSDictionary *params = [self params:call];
  102. float v = [params[@"volume"] floatValue] / 100;
  103. controller.playbackVolume = v;
  104. result(@(YES));
  105. } else if ([@"screenShot" isEqualToString:call.method]) {
  106. __weak typeof(&*self) weakSelf = self;
  107. dispatch_async(dispatch_get_main_queue(), ^{
  108. NSData *data = [weakSelf screenShot];
  109. result(data);
  110. });
  111. } else if ([@"setSpeed" isEqualToString:call.method]) {
  112. float speedValue = [call.arguments floatValue];
  113. if (controller) {
  114. [controller setPlaybackRate:speedValue];
  115. }
  116. result(@YES);
  117. } else {
  118. result(FlutterMethodNotImplemented);
  119. }
  120. }
  121. - (NSDictionary *)params:(FlutterMethodCall *)call {
  122. return call.arguments;
  123. }
  124. + (instancetype)ijkWithRegistrar:(NSObject <FlutterPluginRegistrar> *)registrar {
  125. return [[self alloc] initWithRegistrar:registrar];
  126. }
  127. - (int64_t)id {
  128. return textureId;
  129. }
  130. - (void)play {
  131. [controller play];
  132. if (displayLink) {
  133. displayLink.paused = NO;
  134. }
  135. }
  136. - (void)pause {
  137. [controller pause];
  138. if (displayLink) {
  139. displayLink.paused = YES;
  140. }
  141. }
  142. - (void)stop {
  143. [controller stop];
  144. if (displayLink) {
  145. displayLink.paused = NO;
  146. }
  147. }
  148. - (void)setDataSourceWithController:(IJKFFMoviePlayerController *)ctl result:(CoolFlutterResult *)result {
  149. if (ctl) {
  150. controller = ctl;
  151. [self prepare:result];
  152. }
  153. }
  154. - (IJKFFOptions *)createOption {
  155. IJKFFOptions *options = [IJKFFOptions optionsByDefault];
  156. // see https://www.jianshu.com/p/843c86a9e9ad
  157. // [options setFormatOptionValue:@"fastseek" forKey:@"fflags"];
  158. // [options setFormatOptionIntValue:100 forKey:@"analyzemaxduration"];
  159. // [options setFormatOptionIntValue:1 forKey:@"analyzeduration"];
  160. // [options setFormatOptionIntValue:10240 forKey:@"probesize"];
  161. // [options setFormatOptionIntValue:1 forKey:@"flush_packets"];
  162. // [options setFormatOptionIntValue:5 forKey:@"reconnect"];
  163. // [options setFormatOptionIntValue:5 forKey:@"framedrop"];
  164. // [options setFormatOptionIntValue:1 forKey:@"enable-accurate-seek"];
  165. // [options setPlayerOptionIntValue:0 forKey:@"video-max-frame-width-default"];
  166. // [options setPlayerOptionIntValue:1 forKey:@"videotoolbox"];
  167. for (CoolIjkOption *opt in self.options) {
  168. if (opt) {
  169. NSString* key = opt.key;
  170. BOOL isString = [opt.value isKindOfClass:[NSString class]];
  171. BOOL isInt;
  172. if([opt.value isKindOfClass:[NSNumber class]]){
  173. isInt = strcmp([opt.value objCType], @encode(int)) == 0;
  174. }else{
  175. isInt = NO;
  176. }
  177. switch (opt.type) {
  178. case 0:
  179. if(isString){
  180. [options setFormatOptionValue:opt.value forKey:key];
  181. }else if(isInt){
  182. [options setFormatOptionIntValue:[opt.value intValue] forKey:key];
  183. }
  184. break;
  185. case 1:
  186. if(isString){
  187. [options setCodecOptionValue:opt.value forKey:key];
  188. }else if(isInt){
  189. [options setCodecOptionIntValue:[opt.value intValue] forKey:key];
  190. }
  191. break;
  192. case 2:
  193. if(isString){
  194. [options setSwsOptionValue:opt.value forKey:key];
  195. }else if(isInt){
  196. [options setSwsOptionIntValue:[opt.value intValue] forKey:key];
  197. }
  198. break;
  199. case 3:
  200. if(isString){
  201. [options setPlayerOptionValue:opt.value forKey:key];
  202. }else if(isInt){
  203. [options setPlayerOptionIntValue:[opt.value intValue] forKey:key];
  204. }
  205. break;
  206. default:
  207. break;
  208. }
  209. }
  210. }
  211. return options;
  212. }
  213. - (void)setDataSourceWithUri:(NSString *)uri headers:(NSDictionary *)headers result:(CoolFlutterResult *)result {
  214. IJKFFOptions *options = [self createOption];
  215. if (headers) {
  216. NSMutableString *headerString = [NSMutableString new];
  217. for (NSString *key in headers.allKeys) {
  218. NSString *value = headers[key];
  219. [headerString appendFormat:@"%@:%@", key, value];
  220. [headerString appendString:@"\r\n"];
  221. }
  222. [options setFormatOptionValue:headerString forKey:@"headers"];
  223. }
  224. controller = [[IJKFFMoviePlayerController alloc] initWithContentURLString:uri withOptions:options];
  225. [self prepare:result];
  226. }
  227. - (void)setDegree:(int)d {
  228. degree = d;
  229. }
  230. - (void)prepare:(CoolFlutterResult *)result {
  231. prepareResult = result;
  232. [controller prepareToPlay];
  233. if (displayLink) {
  234. displayLink.paused = YES;
  235. [displayLink invalidate];
  236. displayLink = nil;
  237. }
  238. displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)];
  239. [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  240. notifyChannel = [CoolIjkNotifyChannel channelWithController:controller textureId:textureId registrar:self.registrar];
  241. notifyChannel.infoDelegate = self;
  242. }
  243. - (void)onLoadStateChange {
  244. // IJKMPMovieLoadState loadState = controller.loadState;
  245. if (prepareResult) {
  246. [prepareResult replyResult:@YES];
  247. }
  248. prepareResult = nil;
  249. }
  250. - (IJKFFMoviePlayerController *)createControllerWithAssetName:(NSString *)assetName pkg:(NSString *)pkg {
  251. NSString *asset;
  252. if (!pkg) {
  253. asset = [self.registrar lookupKeyForAsset:assetName];
  254. } else {
  255. asset = [self.registrar lookupKeyForAsset:assetName fromPackage:pkg];
  256. }
  257. NSString *path = [[NSBundle mainBundle] pathForResource:asset ofType:nil];
  258. NSURL *url = [NSURL fileURLWithPath:path];
  259. IJKFFOptions *options = [self createOption];
  260. return [[IJKFFMoviePlayerController alloc] initWithContentURL:url withOptions:options];
  261. }
  262. - (IJKFFMoviePlayerController *)createControllerWithPath:(NSString *)path {
  263. NSURL *url = [NSURL fileURLWithPath:path];
  264. IJKFFOptions *options = [self createOption];
  265. return [[IJKFFMoviePlayerController alloc] initWithContentURL:url withOptions:options];
  266. }
  267. - (void)seekTo:(double)target {
  268. [controller setCurrentPlaybackTime:target];
  269. }
  270. - (void)onDisplayLink:(CADisplayLink *)link {
  271. [textures textureFrameAvailable:textureId];
  272. }
  273. - (CVPixelBufferRef _Nullable)copyPixelBuffer {
  274. CVPixelBufferRef newBuffer = [controller framePixelbuffer];
  275. if (newBuffer) {
  276. CFRetain(newBuffer);
  277. CVPixelBufferRef pixelBuffer = latestPixelBuffer;
  278. while (!OSAtomicCompareAndSwapPtrBarrier(pixelBuffer, newBuffer, (void **) &latestPixelBuffer)) {
  279. pixelBuffer = latestPixelBuffer;
  280. }
  281. return pixelBuffer;
  282. }
  283. return NULL;
  284. }
  285. - (CoolVideoInfo *)getInfo {
  286. CoolVideoInfo *info = [CoolVideoInfo new];
  287. CGSize size = [controller naturalSize];
  288. NSTimeInterval duration = [controller duration];
  289. NSTimeInterval currentPlaybackTime = [controller currentPlaybackTime];
  290. info.size = size;
  291. info.duration = duration;
  292. info.currentPosition = currentPlaybackTime;
  293. info.isPlaying = [controller isPlaying];
  294. info.degree = degree;
  295. info.tcpSpeed = [controller tcpSpeed];
  296. info.outputFps = [controller fpsAtOutput];
  297. return info;
  298. }
  299. - (NSUInteger)degreeFromVideoFileWithURL:(NSURL *)url {
  300. NSUInteger mDegree = 0;
  301. AVAsset *asset = [AVAsset assetWithURL:url];
  302. NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
  303. if ([tracks count] > 0) {
  304. AVAssetTrack *videoTrack = tracks[0];
  305. CGAffineTransform t = videoTrack.preferredTransform;
  306. if (t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0) {
  307. // Portrait
  308. mDegree = 90;
  309. } else if (t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0) {
  310. // PortraitUpsideDown
  311. mDegree = 270;
  312. } else if (t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0) {
  313. // LandscapeRight
  314. mDegree = 0;
  315. } else if (t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0) {
  316. // LandscapeLeft
  317. mDegree = 180;
  318. }
  319. }
  320. return mDegree;
  321. }
  322. - (NSData*) screenShot{
  323. CVPixelBufferRef ref = [self copyPixelBuffer];
  324. if(!ref){
  325. return nil;
  326. }
  327. UIImage *img = [self convertPixeclBufferToUIImage:ref];
  328. return UIImageJPEGRepresentation(img, 1.0);
  329. }
  330. -(UIImage*)convertPixeclBufferToUIImage:(CVPixelBufferRef)pixelBuffer{
  331. CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
  332. CIContext *temporaryContext = [CIContext contextWithOptions:nil];
  333. CGImageRef videoImage = [temporaryContext
  334. createCGImage:ciImage
  335. fromRect:CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer))];
  336. UIImage *uiImage = [UIImage imageWithCGImage:videoImage];
  337. CGImageRelease(videoImage);
  338. return uiImage;
  339. }
  340. @end