CoolFlutterIJK.m 12 KB

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