Open jendy2012 opened 5 years ago
@class PLControlView;
int64_t FLTCMTimeToMillis(CMTime time) { return time.value * 1000 / time.timescale; }
@interface FLTFrameUpdater : NSObject @property(nonatomic) int64_t textureId; @property(nonatomic, readonly) NSObject* registry;
@implementation FLTFrameUpdater
(FLTFrameUpdater)initWithRegistry:(NSObject)registry { NSAssert(self, @"super init cannot be nil"); if (self == nil) return nil; _registry = registry; return self; }
(void)onDisplayLink:(CADisplayLink*)link { [_registry textureFrameAvailable:_textureId]; }
@end
static void timeRangeContext = &timeRangeContext; static void statusContext = &statusContext; static void playbackLikelyToKeepUpContext = &playbackLikelyToKeepUpContext; static void playbackBufferEmptyContext = &playbackBufferEmptyContext; static void* playbackBufferFullContext = &playbackBufferFullContext;
@interface FLTVideoPlayer : NSObject<FlutterTexture, FlutterStreamHandler> @property(readonly, nonatomic) PLPlayer player; @property (nonatomic, strong) UIButton playButton; @property (nonatomic, strong) UIImageView thumbImageView; @property (nonatomic, strong) UIButton closeButton; @property (nonatomic, strong) NSURL url; @property (nonatomic, strong) UIImage thumbImage; @property (nonatomic, strong) NSURL thumbImageURL; //是否启用手指滑动调节音量和亮度, default YES @property (nonatomic, assign) BOOL enableGesture; @property (nonatomic, strong) UIView topBarView; @property (nonatomic, strong) UILabel titleLabel; @property (nonatomic, strong) UIButton moreButton; @property (nonatomic, strong) UIButton *exitfullScreenButton;
@property (nonatomic, strong) UIView bottomBarView; @property (nonatomic, strong) UISlider slider; @property (nonatomic, strong) UILabel playTimeLabel; @property (nonatomic, strong) UILabel durationLabel; @property (nonatomic, strong) UIProgressView bufferingView; @property (nonatomic, strong) UIButton enterFullScreenButton;
// 在bottomBarView上面的播放暂停按钮,全屏的时候,显示 @property (nonatomic, strong) UIButton *pauseButton;
@property (nonatomic, assign) UIDeviceOrientation deviceOrientation;
@property (nonatomic, strong) PLPlayerOption *playerOption; @property (nonatomic, assign) BOOL isNeedSetupPlayer;
@property (nonatomic, strong) NSTimer *playTimer;
// 在屏幕中间的播放和暂停按钮,全屏的时候,隐藏 @property (nonatomic, strong) UIButton centerPlayButton; @property (nonatomic, strong) UIButton centerPauseButton;
@property (nonatomic, strong) UIButton *snapshotButton;
@property (nonatomic, strong) UIPanGestureRecognizer panGesture; @property (nonatomic, strong) UITapGestureRecognizer tapGesture;
@property (nonatomic, strong) PLControlView *controlView;
// 很多时候调用stop之后,播放器可能还会返回请他状态,导致逻辑混乱,记录一下,只要调用了播放器的 stop 方法,就将 isStop 置为 YES 做标记 @property (nonatomic, assign) BOOL isStop;
// 当底部的 bottomBarView 因隐藏的时候,提供两个 progrssview 在最底部,随时能看到播放进度和缓冲进度 @property (nonatomic, strong) UIProgressView bottomPlayProgreeeView; @property (nonatomic, strong) UIProgressView bottomBufferingProgressView;
// 适配iPhone X @property (nonatomic, assign) CGFloat edgeSpace;
@property(readonly, nonatomic) CADisplayLink displayLink; @property(nonatomic) FlutterEventChannel eventChannel; @property(nonatomic) FlutterEventSink eventSink; @property(nonatomic, readonly) bool disposed; @property(nonatomic, readonly) bool isPlaying; @property(nonatomic, readonly) bool isLooping; @property(nonatomic, readonly) bool isInitialized;
@implementation FLTVideoPlayer
(void)VideoView { [UIApplication.sharedApplication.keyWindow.subviews.firstObject addSubview:_player.playerView]; dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 3 NSEC_PER_SEC); dispatch_after(timer, dispatch_get_main_queue(), ^{ for (UIView item in UIApplication.sharedApplication.keyWindow.subviews) { [self setPlayerView:item]; } }); }
(void)setPlayerView:(UIView *)v { if ([v conformsToProtocol:@protocol(FLTMainView)]) { [v addSubview:_player.playerView]; // [v addSubview:_closeButton]; // [v addSubview:_playButton]; }
for (UIView * item in v.subviews) { [self setPlayerView:item]; } }
(instancetype)initWithAsset:(NSString)asset frameUpdater:(FLTFrameUpdater)frameUpdater { NSString* path = [[NSBundle mainBundle] pathForResource:asset ofType:nil]; return [self initWithURL:[NSURL fileURLWithPath:path] frameUpdater:frameUpdater]; }
(instancetype)initWithFrame:(CGRect)frame { self = [super init]; if (self) { // self.backgroundColor = [UIColor blackColor]; if (CGRectEqualToRect([UIScreen mainScreen].bounds, CGRectMake(0, 0, 375, 812))) { // iPhone X self.edgeSpace = 20; } else { self.edgeSpace = 5; }
self.deviceOrientation = UIDeviceOrientationUnknown;
// [self transformWithOrientation:UIDeviceOrientationPortrait];
self.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];
// [self addGestureRecognizer:self.tapGesture];
self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)]; self.panGesture.delegate = self;
} return self; }
(BOOL)isFullScreen { return UIDeviceOrientationPortrait != self.deviceOrientation; }
(void) setupPlayer {
self.enableGesture = YES; }
(instancetype)initWithURL:(NSURL)url frameUpdater:(FLTFrameUpdater)frameUpdater { self = [super init]; NSLog(@"播放地址: %@", _url.absoluteString); PLPlayerOption option = [PLPlayerOption defaultOption]; PLPlayFormat format = kPLPLAY_FORMAT_UnKnown; NSString urlString = _url.absoluteString.lowercaseString; if ([urlString hasSuffix:@"mp4"]) { format = kPLPLAY_FORMAT_MP4; } else if ([urlString hasPrefix:@"rtmp:"]) { format = kPLPLAY_FORMAT_FLV; } else if ([urlString hasSuffix:@".mp3"]) { format = kPLPLAY_FORMAT_MP3; } else if ([urlString hasSuffix:@".m3u8"]) { format = kPLPLAY_FORMAT_M3U8; } [option setOptionValue:@(format) forKey:PLPlayerOptionKeyVideoPreferFormat]; [option setOptionValue:@(kPLLogNone) forKey:PLPlayerOptionKeyLogLevel];
_player = [PLPlayer playerWithURL:url option:option]; _player.delegateQueue = dispatch_get_main_queue(); _player.playerView.contentMode = UIViewContentModeScaleAspectFit; _player.delegate = self; _player.loopPlay = YES; _player.playerView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; [self setupPlayer]; // [self play]; _player.playerView.frame = CGRectMake(0, KIsiPhoneX?100:56, UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height*0.34); [self VideoView]; [self.player play]; return self; }
(void)observeValueForKeyPath:(NSString)path ofObject:(id)object change:(NSDictionary)change context:(void)context { if (context == timeRangeContext) { if (_eventSink != nil) { NSMutableArray<NSArray<NSNumber>> values = [[NSMutableArray alloc] init]; for (NSValue rangeValue in [object loadedTimeRanges]) { CMTimeRange range = [rangeValue CMTimeRangeValue]; int64_t start = FLTCMTimeToMillis(range.start); [values addObject:@[ @(start), @(start + FLTCMTimeToMillis(range.duration)) ]]; } _eventSink(@{@"event" : @"bufferingUpdate", @"values" : values}); } } else if (context == statusContext) { AVPlayerItem item = (AVPlayerItem*)object; switch (item.status) { case AVPlayerStatusFailed: if (_eventSink != nil) { _eventSink([FlutterError errorWithCode:@"VideoError" message:[@"Failed to load video: " stringByAppendingString:[item.error localizedDescription]] details:nil]); } break; case AVPlayerItemStatusUnknown: break; case AVPlayerItemStatusReadyToPlay: _isInitialized = true; // [item addOutput:_videoOutput]; [self sendInitialized]; [self updatePlayingState]; break; } } else if (context == playbackBufferEmptyContext) { if (_eventSink != nil) { _eventSink(@{@"event" : @"bufferingStart"}); } } else if (context == playbackBufferFullContext) { if (_eventSink != nil) { _eventSink(@{@"event" : @"bufferingEnd"}); } } }
(void)updatePlayingState { if (!_isInitialized) { return; } if (_isPlaying) { [_player play]; } else { [_player pause]; } _displayLink.paused = !_isPlaying; }
(void)sendInitialized {
if (_eventSink && _isInitialized) { _eventSink(@{ @"event" : @"initialized", @"duration" : @([self duration]), @"width" : @(_player.width), @"height" : @(_player.height), }); } }
(int64_t)position { return FLTCMTimeToMillis(_player.currentTime); // return _player.currentTime; }
(int64_t)duration { return FLTCMTimeToMillis(_player.totalDuration); // return _player.totalDuration; }
(void)seekTo:(int)location { [_player seekTo:(CMTimeMake(location, 1000))]; }
(void)setIsLooping:(bool)isLooping { [_player setLoopPlay:isLooping]; _isLooping = isLooping; }
(void)setVolume:(double)volume {
_player.volume = (volume < 0.0) ? 0.0 : ((volume > 1.0) ? 1.0 : volume); }
(FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments { _eventSink = nil; return nil; }
(FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { _eventSink = events; [self sendInitialized]; return nil; }
(void)dispose { _disposed = true; [_displayLink invalidate]; [[NSNotificationCenter defaultCenter] removeObserver:self]; _player.playerView.frame = CGRectMake(0, 0, 0, 0); [_player stop]; [_eventChannel setStreamHandler:nil]; }
(void)stop { [_player stop]; [[UIApplication sharedApplication] setIdleTimerDisabled:NO]; }
(void)singleTapAction:(UIGestureRecognizer *)gesture { if ([self.player isPlaying]) { [self.player pause]; } else { [self.player resume]; } }
(void)showWaiting {
// [self.playButton hide]; // [_player.playerView showFullLoading]; // [self.view bringSubviewToFront:self.closeButton]; }
(void)clickPlayButton:(UIButton *)button { if ([self.player isPlaying]) { [self.player pause]; } else { [self.player resume]; } }
(void)play { self.isStop = NO; [self resetButton:YES];
if (!(PLPlayerStatusReady == self.player.status || PLPlayerStatusOpen == self.player.status || PLPlayerStatusCaching == self.player.status || PLPlayerStatusPlaying == self.player.status || PLPlayerStatusPreparing == self.player.status) ) { NSDate *date = [NSDate date]; [self.player play]; NSLog(@"play 耗时: %f s",[[NSDate date] timeIntervalSinceDate:date]); } }
(void)pause { [self.player pause]; [self resetButton:NO]; }
(void)resume { [self.player resume]; [self resetButton:YES]; }
(void)resetButton:(BOOL)isPlaying {
self.playButton.hidden = isPlaying; self.pauseButton.hidden = !isPlaying;
if (isPlaying) { self.centerPauseButton.hidden = NO; self.centerPlayButton.hidden = YES; } else { self.centerPauseButton.hidden = YES; // [self.centerPlayButton show]; } }
(void)playerWillBeginBackgroundTask:(PLPlayer *)player { }
(void)playerWillEndBackgroundTask:(PLPlayer *)player { }
(void)player:(PLPlayer )player statusDidChange:(PLPlayerStatus)state { if (self.isStop) { static NSString statesString[] = { @"PLPlayerStatusUnknow" @"PLPlayerStatusPreparing", @"PLPlayerStatusReady", @"PLPlayerStatusOpen", @"PLPlayerStatusCaching", @"PLPlayerStatusPlaying", @"PLPlayerStatusPaused", @"PLPlayerStatusStopped", @"PLPlayerStatusError", @"PLPlayerStateAutoReconnecting", @"PLPlayerStatusCompleted" }; // NSLog(@"stop statusDidChange self,= %p state = %@", self, statesString[state]); [self stop]; return; }
if (state == PLPlayerStatusPlaying || state == PLPlayerStatusPaused || state == PLPlayerStatusStopped || state == PLPlayerStatusError || state == PLPlayerStatusUnknow || state == PLPlayerStatusCompleted) { // [self hideFullLoading]; } else if (state == PLPlayerStatusPreparing || state == PLPlayerStatusReady || state == PLPlayerStatusCaching) { // [self showFullLoading]; self.centerPauseButton.hidden = YES; } else if (state == PLPlayerStateAutoReconnecting) { // [self showFullLoading]; self.centerPauseButton.hidden = YES; // alert 重新 // [self showTip:@"重新连接..."]; }
//开始播放之后,如果 bar 是显示的,则 3 秒之后自动隐藏 if (PLPlayerStatusPlaying == state) { // if (self.bottomBarView.frame.origin.y >= self.bounds.size.height) { // [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideBar) object:nil]; // [self performSelector:@selector(hideBar) withObject:nil afterDelay:3]; // } } }
// AppDelegate实现 @interface PlPlayerPlugin () @property(readonly, nonatomic) NSObject registry; @property(readonly, nonatomic) NSObject messenger; @property(readonly, nonatomic) NSMutableDictionary players; //创建词典对象 @property(readonly, nonatomic) NSObject registrar; @end
// Registrar 声明和实现 @implementation PlPlayerPlugin
(void)registerWithRegistrar:(NSObject)registrar { FlutterMethodChannel channel = [FlutterMethodChannel methodChannelWithName:@"pl_player" binaryMessenger:[registrar messenger]]; PlPlayerPlugin* instance = [[PlPlayerPlugin alloc] initWithRegistrar:registrar]; [registrar addMethodCallDelegate:instance channel:channel]; }
(instancetype)initWithRegistrar:(NSObject*)registrar { self = [super init];
NSAssert(self, @"super init cannot be nil"); _registry = [registrar textures]; _messenger = [registrar messenger]; _registrar = registrar;
//创建一个可变词典初始指定它的长度为1.,动态的添加数据如果超过1,这个词典长度会自动增加,所以不用担心数组越界。推荐用这种方式 _players = [NSMutableDictionary dictionaryWithCapacity:1]; return self; }
(void)handleMethodCall:(FlutterMethodCall)call result:(FlutterResult)result { if ([@"getPlatformVersion" isEqualToString:call.method]) { result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); }else if ([@"init" isEqualToString:call.method]) { // Allow audio playback when the Ring/Silent switch is set to silent [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; for (NSNumber textureId in _players) { [_registry unregisterTexture:[textureId unsignedIntegerValue]]; [[_players objectForKey:textureId] dispose]; } [_players removeAllObjects]; }else if ([@"create" isEqualToString:call.method]) { NSDictionary argsMap = call.arguments; NSString dataSource = argsMap[@"uri"]; FLTFrameUpdater frameUpdater = [[FLTFrameUpdater alloc] initWithRegistry:_registry]; FLTVideoPlayer player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:dataSource] frameUpdater:frameUpdater]; int64_t textureId = [_registry registerTexture:player]; frameUpdater.textureId = textureId; FlutterEventChannel eventChannel = [FlutterEventChannel eventChannelWithName:[NSString stringWithFormat:@"pl_player/videoEvents%lld", textureId] binaryMessenger:_messenger]; [eventChannel setStreamHandler:player]; player.eventChannel = eventChannel; _players[@(textureId)] = player; result(@{ @"textureId" : @(textureId) }); // // NSDictionary argsMap = call.arguments; // NSString dataSource = argsMap[@"uri"]; //// PLPlayer player; // FLTFrameUpdater frameUpdater = [[FLTFrameUpdater alloc] initWithRegistry:_registry]; // FLTVideoPlayer player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:dataSource] // frameUpdater:frameUpdater]; // int64_t textureId = [_registry registerTexture:player]; // frameUpdater.textureId = textureId; // FlutterEventChannel eventChannel = [FlutterEventChannel // eventChannelWithName:[NSString stringWithFormat:@"pl_player/videoEvents%lld", // textureId] // binaryMessenger:_messenger]; // [eventChannel setStreamHandler:player]; //// player.eventChannel = eventChannel; // _players[@(textureId)] = player; // result(@{ @"textureId" : @(textureId) }); } else { NSDictionary argsMap = call.arguments; int64_t textureId = ((NSNumber)argsMap[@"textureId"]).unsignedIntegerValue; FLTVideoPlayer player = _players[@(textureId)]; if ([@"dispose" isEqualToString:call.method]) { [_registry unregisterTexture:textureId]; [_players removeObjectForKey:@(textureId)]; [player dispose]; } else if ([@"setLooping" isEqualToString:call.method]) { [player setIsLooping:[argsMap objectForKey:@"looping"]]; result(nil); } else if ([@"setVolume" isEqualToString:call.method]) { [player setVolume:[[argsMap objectForKey:@"volume"] doubleValue]]; result(nil); } else if ([@"play" isEqualToString:call.method]) { [player play]; result(nil); } else if ([@"position" isEqualToString:call.method]) { // result(@([player position])); } else if ([@"seekTo" isEqualToString:call.method]) { [player seekTo:[[argsMap objectForKey:@"location"] intValue]]; result(nil); } else if ([@"pause" isEqualToString:call.method]) { [player pause]; result(nil); } else { result(FlutterMethodNotImplemented); } } }
测试可以播放。但是需要整合一下。
@jendy2012
Thank you for your contribution, I will add it when I have time.
import "PlPlayerPlugin.h"
import <AVFoundation/AVFoundation.h>
import <PLPlayerKit/PLPlayer.h>
@class PLControlView;
int64_t FLTCMTimeToMillis(CMTime time) { return time.value * 1000 / time.timescale; }
@interface FLTFrameUpdater : NSObject @property(nonatomic) int64_t textureId; @property(nonatomic, readonly) NSObject* registry;
@implementation FLTFrameUpdater
(FLTFrameUpdater)initWithRegistry:(NSObject )registry {
NSAssert(self, @"super init cannot be nil");
if (self == nil) return nil;
_registry = registry;
return self;
}
(void)onDisplayLink:(CADisplayLink*)link { [_registry textureFrameAvailable:_textureId]; }
@end
static void timeRangeContext = &timeRangeContext; static void statusContext = &statusContext; static void playbackLikelyToKeepUpContext = &playbackLikelyToKeepUpContext; static void playbackBufferEmptyContext = &playbackBufferEmptyContext; static void* playbackBufferFullContext = &playbackBufferFullContext;
@interface FLTVideoPlayer : NSObject<FlutterTexture, FlutterStreamHandler> @property(readonly, nonatomic) PLPlayer player; @property (nonatomic, strong) UIButton playButton; @property (nonatomic, strong) UIImageView thumbImageView; @property (nonatomic, strong) UIButton closeButton; @property (nonatomic, strong) NSURL url; @property (nonatomic, strong) UIImage thumbImage; @property (nonatomic, strong) NSURL thumbImageURL; //是否启用手指滑动调节音量和亮度, default YES @property (nonatomic, assign) BOOL enableGesture; @property (nonatomic, strong) UIView topBarView; @property (nonatomic, strong) UILabel titleLabel; @property (nonatomic, strong) UIButton moreButton; @property (nonatomic, strong) UIButton *exitfullScreenButton;
@property (nonatomic, strong) UIView bottomBarView; @property (nonatomic, strong) UISlider slider; @property (nonatomic, strong) UILabel playTimeLabel; @property (nonatomic, strong) UILabel durationLabel; @property (nonatomic, strong) UIProgressView bufferingView; @property (nonatomic, strong) UIButton enterFullScreenButton;
// 在bottomBarView上面的播放暂停按钮,全屏的时候,显示 @property (nonatomic, strong) UIButton *pauseButton;
@property (nonatomic, assign) UIDeviceOrientation deviceOrientation;
@property (nonatomic, strong) PLPlayerOption *playerOption; @property (nonatomic, assign) BOOL isNeedSetupPlayer;
@property (nonatomic, strong) NSTimer *playTimer;
// 在屏幕中间的播放和暂停按钮,全屏的时候,隐藏 @property (nonatomic, strong) UIButton centerPlayButton; @property (nonatomic, strong) UIButton centerPauseButton;
@property (nonatomic, strong) UIButton *snapshotButton;
@property (nonatomic, strong) UIPanGestureRecognizer panGesture; @property (nonatomic, strong) UITapGestureRecognizer tapGesture;
@property (nonatomic, strong) PLControlView *controlView;
// 很多时候调用stop之后,播放器可能还会返回请他状态,导致逻辑混乱,记录一下,只要调用了播放器的 stop 方法,就将 isStop 置为 YES 做标记 @property (nonatomic, assign) BOOL isStop;
// 当底部的 bottomBarView 因隐藏的时候,提供两个 progrssview 在最底部,随时能看到播放进度和缓冲进度 @property (nonatomic, strong) UIProgressView bottomPlayProgreeeView; @property (nonatomic, strong) UIProgressView bottomBufferingProgressView;
// 适配iPhone X @property (nonatomic, assign) CGFloat edgeSpace;
@property(readonly, nonatomic) CADisplayLink displayLink; @property(nonatomic) FlutterEventChannel eventChannel; @property(nonatomic) FlutterEventSink eventSink; @property(nonatomic, readonly) bool disposed; @property(nonatomic, readonly) bool isPlaying; @property(nonatomic, readonly) bool isLooping; @property(nonatomic, readonly) bool isInitialized;
@implementation FLTVideoPlayer
define KIsiPhoneX ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO)
(void)VideoView { [UIApplication.sharedApplication.keyWindow.subviews.firstObject addSubview:_player.playerView]; dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 3 NSEC_PER_SEC); dispatch_after(timer, dispatch_get_main_queue(), ^{ for (UIView item in UIApplication.sharedApplication.keyWindow.subviews) { [self setPlayerView:item]; } }); }
(void)setPlayerView:(UIView *)v { if ([v conformsToProtocol:@protocol(FLTMainView)]) { [v addSubview:_player.playerView]; // [v addSubview:_closeButton]; // [v addSubview:_playButton]; }
for (UIView * item in v.subviews) { [self setPlayerView:item]; } }
(instancetype)initWithAsset:(NSString)asset frameUpdater:(FLTFrameUpdater)frameUpdater { NSString* path = [[NSBundle mainBundle] pathForResource:asset ofType:nil]; return [self initWithURL:[NSURL fileURLWithPath:path] frameUpdater:frameUpdater]; }
(instancetype)initWithFrame:(CGRect)frame { self = [super init]; if (self) { // self.backgroundColor = [UIColor blackColor]; if (CGRectEqualToRect([UIScreen mainScreen].bounds, CGRectMake(0, 0, 375, 812))) { // iPhone X self.edgeSpace = 20; } else { self.edgeSpace = 5; }
// [self transformWithOrientation:UIDeviceOrientationPortrait];
// [self addGestureRecognizer:self.tapGesture];
} return self; }
(BOOL)isFullScreen { return UIDeviceOrientationPortrait != self.deviceOrientation; }
(void) setupPlayer {
self.enableGesture = YES; }
(instancetype)initWithURL:(NSURL)url frameUpdater:(FLTFrameUpdater)frameUpdater { self = [super init]; NSLog(@"播放地址: %@", _url.absoluteString); PLPlayerOption option = [PLPlayerOption defaultOption]; PLPlayFormat format = kPLPLAY_FORMAT_UnKnown; NSString urlString = _url.absoluteString.lowercaseString; if ([urlString hasSuffix:@"mp4"]) { format = kPLPLAY_FORMAT_MP4; } else if ([urlString hasPrefix:@"rtmp:"]) { format = kPLPLAY_FORMAT_FLV; } else if ([urlString hasSuffix:@".mp3"]) { format = kPLPLAY_FORMAT_MP3; } else if ([urlString hasSuffix:@".m3u8"]) { format = kPLPLAY_FORMAT_M3U8; } [option setOptionValue:@(format) forKey:PLPlayerOptionKeyVideoPreferFormat]; [option setOptionValue:@(kPLLogNone) forKey:PLPlayerOptionKeyLogLevel];
_player = [PLPlayer playerWithURL:url option:option]; _player.delegateQueue = dispatch_get_main_queue(); _player.playerView.contentMode = UIViewContentModeScaleAspectFit; _player.delegate = self; _player.loopPlay = YES; _player.playerView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; [self setupPlayer]; // [self play]; _player.playerView.frame = CGRectMake(0, KIsiPhoneX?100:56, UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height*0.34); [self VideoView]; [self.player play]; return self; }
(void)observeValueForKeyPath:(NSString)path ofObject:(id)object change:(NSDictionary)change context:(void)context { if (context == timeRangeContext) { if (_eventSink != nil) { NSMutableArray<NSArray<NSNumber>> values = [[NSMutableArray alloc] init]; for (NSValue rangeValue in [object loadedTimeRanges]) { CMTimeRange range = [rangeValue CMTimeRangeValue]; int64_t start = FLTCMTimeToMillis(range.start); [values addObject:@[ @(start), @(start + FLTCMTimeToMillis(range.duration)) ]]; } _eventSink(@{@"event" : @"bufferingUpdate", @"values" : values}); } } else if (context == statusContext) { AVPlayerItem item = (AVPlayerItem*)object; switch (item.status) { case AVPlayerStatusFailed: if (_eventSink != nil) { _eventSink([FlutterError errorWithCode:@"VideoError" message:[@"Failed to load video: " stringByAppendingString:[item.error localizedDescription]] details:nil]); } break; case AVPlayerItemStatusUnknown: break; case AVPlayerItemStatusReadyToPlay: _isInitialized = true; // [item addOutput:_videoOutput]; [self sendInitialized]; [self updatePlayingState]; break; } } else if (context == playbackBufferEmptyContext) { if (_eventSink != nil) { _eventSink(@{@"event" : @"bufferingStart"}); } } else if (context == playbackBufferFullContext) { if (_eventSink != nil) { _eventSink(@{@"event" : @"bufferingEnd"}); } } }
(void)updatePlayingState { if (!_isInitialized) { return; } if (_isPlaying) { [_player play]; } else { [_player pause]; } _displayLink.paused = !_isPlaying; }
(void)sendInitialized {
if (_eventSink && _isInitialized) { _eventSink(@{ @"event" : @"initialized", @"duration" : @([self duration]), @"width" : @(_player.width), @"height" : @(_player.height), }); } }
(int64_t)position { return FLTCMTimeToMillis(_player.currentTime); // return _player.currentTime; }
(int64_t)duration { return FLTCMTimeToMillis(_player.totalDuration); // return _player.totalDuration; }
(void)seekTo:(int)location { [_player seekTo:(CMTimeMake(location, 1000))]; }
(void)setIsLooping:(bool)isLooping { [_player setLoopPlay:isLooping]; _isLooping = isLooping; }
(void)setVolume:(double)volume {
_player.volume = (volume < 0.0) ? 0.0 : ((volume > 1.0) ? 1.0 : volume); }
(FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments { _eventSink = nil; return nil; }
(FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { _eventSink = events; [self sendInitialized]; return nil; }
(void)dispose { _disposed = true; [_displayLink invalidate]; [[NSNotificationCenter defaultCenter] removeObserver:self]; _player.playerView.frame = CGRectMake(0, 0, 0, 0); [_player stop]; [_eventChannel setStreamHandler:nil]; }
(void)stop { [_player stop]; [[UIApplication sharedApplication] setIdleTimerDisabled:NO]; }
(void)singleTapAction:(UIGestureRecognizer *)gesture { if ([self.player isPlaying]) { [self.player pause]; } else { [self.player resume]; } }
(void)showWaiting {
// [self.playButton hide]; // [_player.playerView showFullLoading]; // [self.view bringSubviewToFront:self.closeButton]; }
(void)clickPlayButton:(UIButton *)button { if ([self.player isPlaying]) { [self.player pause]; } else { [self.player resume]; } }
(void)play { self.isStop = NO; [self resetButton:YES];
if (!(PLPlayerStatusReady == self.player.status || PLPlayerStatusOpen == self.player.status || PLPlayerStatusCaching == self.player.status || PLPlayerStatusPlaying == self.player.status || PLPlayerStatusPreparing == self.player.status) ) { NSDate *date = [NSDate date]; [self.player play]; NSLog(@"play 耗时: %f s",[[NSDate date] timeIntervalSinceDate:date]); } }
(void)pause { [self.player pause]; [self resetButton:NO]; }
(void)resume { [self.player resume]; [self resetButton:YES]; }
(void)resetButton:(BOOL)isPlaying {
self.playButton.hidden = isPlaying; self.pauseButton.hidden = !isPlaying;
if (isPlaying) { self.centerPauseButton.hidden = NO; self.centerPlayButton.hidden = YES; } else { self.centerPauseButton.hidden = YES; // [self.centerPlayButton show]; } }
pragma mark - PLPlayerDelegate
(void)playerWillBeginBackgroundTask:(PLPlayer *)player { }
(void)playerWillEndBackgroundTask:(PLPlayer *)player { }
(void)player:(PLPlayer )player statusDidChange:(PLPlayerStatus)state { if (self.isStop) { static NSString statesString[] = { @"PLPlayerStatusUnknow" @"PLPlayerStatusPreparing", @"PLPlayerStatusReady", @"PLPlayerStatusOpen", @"PLPlayerStatusCaching", @"PLPlayerStatusPlaying", @"PLPlayerStatusPaused", @"PLPlayerStatusStopped", @"PLPlayerStatusError", @"PLPlayerStateAutoReconnecting", @"PLPlayerStatusCompleted" }; // NSLog(@"stop statusDidChange self,= %p state = %@", self, statesString[state]); [self stop]; return; }
if (state == PLPlayerStatusPlaying || state == PLPlayerStatusPaused || state == PLPlayerStatusStopped || state == PLPlayerStatusError || state == PLPlayerStatusUnknow || state == PLPlayerStatusCompleted) { // [self hideFullLoading]; } else if (state == PLPlayerStatusPreparing || state == PLPlayerStatusReady || state == PLPlayerStatusCaching) { // [self showFullLoading]; self.centerPauseButton.hidden = YES; } else if (state == PLPlayerStateAutoReconnecting) { // [self showFullLoading]; self.centerPauseButton.hidden = YES; // alert 重新 // [self showTip:@"重新连接..."]; }
//开始播放之后,如果 bar 是显示的,则 3 秒之后自动隐藏 if (PLPlayerStatusPlaying == state) { // if (self.bottomBarView.frame.origin.y >= self.bounds.size.height) { // [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideBar) object:nil]; // [self performSelector:@selector(hideBar) withObject:nil afterDelay:3]; // } } }
@end
// AppDelegate实现 @interface PlPlayerPlugin () @property(readonly, nonatomic) NSObject registry;
@property(readonly, nonatomic) NSObject messenger;
@property(readonly, nonatomic) NSMutableDictionary players;
//创建词典对象
@property(readonly, nonatomic) NSObject registrar;
@end
// Registrar 声明和实现 @implementation PlPlayerPlugin
(void)registerWithRegistrar:(NSObject)registrar {
FlutterMethodChannel channel = [FlutterMethodChannel
methodChannelWithName:@"pl_player"
binaryMessenger:[registrar messenger]];
PlPlayerPlugin* instance = [[PlPlayerPlugin alloc] initWithRegistrar:registrar];
[registrar addMethodCallDelegate:instance channel:channel];
}
(instancetype)initWithRegistrar:(NSObject*)registrar {
self = [super init];
NSAssert(self, @"super init cannot be nil"); _registry = [registrar textures]; _messenger = [registrar messenger]; _registrar = registrar;
//创建一个可变词典初始指定它的长度为1.,动态的添加数据如果超过1,这个词典长度会自动增加,所以不用担心数组越界。推荐用这种方式 _players = [NSMutableDictionary dictionaryWithCapacity:1]; return self; }
(void)handleMethodCall:(FlutterMethodCall)call result:(FlutterResult)result { if ([@"getPlatformVersion" isEqualToString:call.method]) { result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); }else if ([@"init" isEqualToString:call.method]) { // Allow audio playback when the Ring/Silent switch is set to silent [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; for (NSNumber textureId in _players) { [_registry unregisterTexture:[textureId unsignedIntegerValue]]; [[_players objectForKey:textureId] dispose]; } [_players removeAllObjects]; }else if ([@"create" isEqualToString:call.method]) { NSDictionary argsMap = call.arguments; NSString dataSource = argsMap[@"uri"]; FLTFrameUpdater frameUpdater = [[FLTFrameUpdater alloc] initWithRegistry:_registry]; FLTVideoPlayer player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:dataSource] frameUpdater:frameUpdater]; int64_t textureId = [_registry registerTexture:player]; frameUpdater.textureId = textureId; FlutterEventChannel eventChannel = [FlutterEventChannel eventChannelWithName:[NSString stringWithFormat:@"pl_player/videoEvents%lld", textureId] binaryMessenger:_messenger]; [eventChannel setStreamHandler:player]; player.eventChannel = eventChannel; _players[@(textureId)] = player; result(@{ @"textureId" : @(textureId) }); // // NSDictionary argsMap = call.arguments; // NSString dataSource = argsMap[@"uri"]; //// PLPlayer player; // FLTFrameUpdater frameUpdater = [[FLTFrameUpdater alloc] initWithRegistry:_registry]; // FLTVideoPlayer player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:dataSource] // frameUpdater:frameUpdater]; // int64_t textureId = [_registry registerTexture:player]; // frameUpdater.textureId = textureId; // FlutterEventChannel eventChannel = [FlutterEventChannel // eventChannelWithName:[NSString stringWithFormat:@"pl_player/videoEvents%lld", // textureId] // binaryMessenger:_messenger]; // [eventChannel setStreamHandler:player]; //// player.eventChannel = eventChannel; // _players[@(textureId)] = player; // result(@{ @"textureId" : @(textureId) }); } else { NSDictionary argsMap = call.arguments; int64_t textureId = ((NSNumber)argsMap[@"textureId"]).unsignedIntegerValue; FLTVideoPlayer player = _players[@(textureId)]; if ([@"dispose" isEqualToString:call.method]) { [_registry unregisterTexture:textureId]; [_players removeObjectForKey:@(textureId)]; [player dispose]; } else if ([@"setLooping" isEqualToString:call.method]) { [player setIsLooping:[argsMap objectForKey:@"looping"]]; result(nil); } else if ([@"setVolume" isEqualToString:call.method]) { [player setVolume:[[argsMap objectForKey:@"volume"] doubleValue]]; result(nil); } else if ([@"play" isEqualToString:call.method]) { [player play]; result(nil); } else if ([@"position" isEqualToString:call.method]) { // result(@([player position])); } else if ([@"seekTo" isEqualToString:call.method]) { [player seekTo:[[argsMap objectForKey:@"location"] intValue]]; result(nil); } else if ([@"pause" isEqualToString:call.method]) { [player pause]; result(nil); } else { result(FlutterMethodNotImplemented); } } }
@end