diff --git a/CONTENTS.md b/CONTENTS.md index d44ee41..358bbb7 100644 --- a/CONTENTS.md +++ b/CONTENTS.md @@ -1,6 +1,6 @@ -# Contents of this open source +# Contents of first open source - Open source code for playing intermediate layer (including: instance management, interface calls, etc.) - RedPlayer is open source in the form of SDK this time diff --git a/README.md b/README.md index cdd4121..bb05854 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ ![示例图片](./redplayer.jpg) ![GitHub release (latest by date including pre-releases)](https://img.shields.io/badge/release-v1.0.0-blue) -![GitHub issues](https://img.shields.io/github/issues/badges/RedPlayer/open) -![GitHub pull requests](https://img.shields.io/bitbucket/pr/badge/RedPlayer?label=pull%20requests) ![GitHub license](https://img.shields.io/badge/license-LGPL2.1-blue) + + Platform | Build Status -------- | ------------ @@ -64,6 +64,9 @@ ### Open Content + ```bash + # Describe the main contents of current open source and the estimated time and contents of the next open source + ``` - [CONTENTS.md](CONTENTS.md) ### Usage diff --git a/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/api/IMediaPlayer.java b/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/api/IMediaPlayer.java index 926372c..9be18e3 100644 --- a/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/api/IMediaPlayer.java +++ b/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/api/IMediaPlayer.java @@ -104,6 +104,8 @@ public interface IMediaPlayer { */ void setVideoCacheDir(String dir); + void setLiveMode(boolean enable); + void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; void setDataSource(Context context, Uri uri, Map headers) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; diff --git a/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/impl/android/AndroidMediaPlayer.java b/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/impl/android/AndroidMediaPlayer.java index 5ec6332..abecfed 100644 --- a/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/impl/android/AndroidMediaPlayer.java +++ b/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/impl/android/AndroidMediaPlayer.java @@ -138,6 +138,10 @@ public class AndroidMediaPlayer extends AbstractMediaPlayer { public void setVideoCacheDir(String dir) { } + @Override + public void setLiveMode(boolean enable) { + } + @Override public String getAudioCodecInfo() { return "unknown"; diff --git a/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/impl/redplayer/RedMediaPlayer.java b/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/impl/redplayer/RedMediaPlayer.java index cfb04ca..363ead0 100644 --- a/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/impl/redplayer/RedMediaPlayer.java +++ b/source/android/OpenRedPlayerCore/src/main/java/com/xingin/openredplayercore/core/impl/redplayer/RedMediaPlayer.java @@ -323,6 +323,13 @@ public class RedMediaPlayer extends AbstractMediaPlayer { private native void _setVideoCacheDir(String dir); + @Override + public void setLiveMode(boolean enable) { + _setLiveMode(enable); + } + + private native void _setLiveMode(boolean enable); + @Override public String getAudioCodecInfo() { diff --git a/source/android/app/build.gradle b/source/android/app/build.gradle index 158e2b1..9367cd5 100644 --- a/source/android/app/build.gradle +++ b/source/android/app/build.gradle @@ -27,7 +27,7 @@ android { buildTypes { release { - minifyEnabled true + minifyEnabled false signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } diff --git a/source/android/app/src/main/AndroidManifest.xml b/source/android/app/src/main/AndroidManifest.xml index e2acca6..f269d28 100644 --- a/source/android/app/src/main/AndroidManifest.xml +++ b/source/android/app/src/main/AndroidManifest.xml @@ -47,6 +47,11 @@ android:name=".input.XhsInputActivity" android:screenOrientation="portrait" /> + + + () private var playIndex = 0 @@ -124,6 +126,7 @@ class XhsPlayerActivity : AppCompatActivity(), XhsSectionAdapter.OnSectionItemCl private fun initData() { playIndex = intent.getIntExtra(INTENT_KEY_INDEX, 0) isShowLoading = intent.getBooleanExtra(INTENT_KEY_SHOW_LOADING, false) + isLive = intent.getBooleanExtra(INTENT_KEY_IS_LIVE, false) val list = intent.getSerializableExtra(INTENT_KEY_URLS) as List videoUrls.addAll(list) // init config @@ -236,26 +239,34 @@ class XhsPlayerActivity : AppCompatActivity(), XhsSectionAdapter.OnSectionItemCl /** update the video play progress */ private fun initVideoProgressView() { progressSeekBar = findViewById(R.id.video_view_seekbar) - progressSeekBar.setOnProgressChangedListener(object : - XhsProgressBar.OnProgressChangedListener { - override fun onProgressChanged( - signSeekBar: XhsProgressBar, progress: Int, progressFloat: Float, fromUser: Boolean - ) { - } + if (!isLive) { + progressSeekBar.setOnProgressChangedListener(object : + XhsProgressBar.OnProgressChangedListener { + override fun onProgressChanged( + signSeekBar: XhsProgressBar, + progress: Int, + progressFloat: Float, + fromUser: Boolean + ) { + } - override fun getProgressOnActionUp( - signSeekBar: XhsProgressBar, progress: Int, progressFloat: Float - ) { - val percentage = progress / 1000.0 - videoPlayerView.seekTo((percentage * videoPlayerView.duration).toInt()) - progressSeekBar.setProgress(progressFloat) - } + override fun getProgressOnActionUp( + signSeekBar: XhsProgressBar, progress: Int, progressFloat: Float + ) { + val percentage = progress / 1000.0 + videoPlayerView.seekTo((percentage * videoPlayerView.duration).toInt()) + progressSeekBar.setProgress(progressFloat) + } - override fun getProgressOnFinally( - signSeekBar: XhsProgressBar, progress: Int, progressFloat: Float, fromUser: Boolean - ) { - } - }) + override fun getProgressOnFinally( + signSeekBar: XhsProgressBar, + progress: Int, + progressFloat: Float, + fromUser: Boolean + ) { + } + }) + } // update the seekbar val handler = Handler(Looper.getMainLooper()) var position: Int @@ -342,7 +353,10 @@ class XhsPlayerActivity : AppCompatActivity(), XhsSectionAdapter.OnSectionItemCl } videoPlayerView.setOnErrorListener { _, what, extra -> handleVideoErrorEvent(what, extra) } // 1.1 set the video url and wait play - videoPlayerView.setVideoPath(getCurrentPlayUrl()) + videoPlayerView.setVideoPath(getCurrentPlayUrl(), isLive) + if (isLive) { + playPauseButton.visibility = View.GONE + } } /** init the video debug info view */ @@ -539,7 +553,7 @@ class XhsPlayerActivity : AppCompatActivity(), XhsSectionAdapter.OnSectionItemCl /** handle video speed play: support 0.75,1.0,1.25,1.5,2.0 */ private fun handleVideoSpeedPlay(speed: Float, isShow: Boolean) { - if (!videoPlayerView.isPlaying) { + if (!videoPlayerView.isPlaying || isLive) { return } videoPlayerView.setSpeed(speed) diff --git a/source/android/app/src/main/java/com/xingin/openredplayer/player/XhsVideoPlayerView.java b/source/android/app/src/main/java/com/xingin/openredplayer/player/XhsVideoPlayerView.java index 44b6739..7e5ef97 100644 --- a/source/android/app/src/main/java/com/xingin/openredplayer/player/XhsVideoPlayerView.java +++ b/source/android/app/src/main/java/com/xingin/openredplayer/player/XhsVideoPlayerView.java @@ -29,6 +29,7 @@ import java.io.IOException; public class XhsVideoPlayerView extends FrameLayout implements MediaController.MediaPlayerControl { private String TAG = "VideoPlayerView"; private String mUrl; + private Boolean mIsLive; private static final int STATE_ERROR = -1; private static final int STATE_IDLE = 0; private static final int STATE_PREPARING = 1; @@ -136,6 +137,11 @@ public class XhsVideoPlayerView extends FrameLayout implements MediaController.M } } + public void setVideoPath(String path, boolean isLive) { + mIsLive = isLive; + setVideoPath(path); + } + public void setVideoPath(String path) { mUrl = path; mSeekWhenPrepared = 0; @@ -217,10 +223,11 @@ public class XhsVideoPlayerView extends FrameLayout implements MediaController.M private IMediaPlayer createMediaPlayer() { IMediaPlayer mediaPlayer = mSettings.getEnableAndroidMediaPlayer() ? new AndroidMediaPlayer() : new RedMediaPlayer(); - // 设置播放器使用硬解: 具体是否有硬解可用,内核来决定 mediaPlayer.setEnableMediaCodec(mSettings.getUsingMediaCodec()); - // 设置视频缓存路径: 边播边下的视频缓存路径,设置后无网可播播过的视频 + // set video cache url: downloading when playing, if network error, can also play mediaPlayer.setVideoCacheDir(Utils.INSTANCE.getVideCacheDir()); + // set whether url is live + mediaPlayer.setLiveMode(mIsLive); return mediaPlayer; } diff --git a/source/android/app/src/main/java/com/xingin/openredplayer/setting/XhsPlayerSettings.kt b/source/android/app/src/main/java/com/xingin/openredplayer/setting/XhsPlayerSettings.kt index 366ea96..b1f87c5 100644 --- a/source/android/app/src/main/java/com/xingin/openredplayer/setting/XhsPlayerSettings.kt +++ b/source/android/app/src/main/java/com/xingin/openredplayer/setting/XhsPlayerSettings.kt @@ -29,7 +29,7 @@ class XhsPlayerSettings(context: Context) { val usingMediaCodec: Boolean get() { val key = mAppContext.getString(R.string.pref_key_using_media_codec) - return mSharedPreferences.getBoolean(key, false) + return mSharedPreferences.getBoolean(key, true) } val enableAndroidMediaPlayer: Boolean diff --git a/source/android/app/src/main/res/layout/fragment_xhs_home_layout.xml b/source/android/app/src/main/res/layout/fragment_xhs_home_layout.xml index 01b1e26..f4cffba 100644 --- a/source/android/app/src/main/res/layout/fragment_xhs_home_layout.xml +++ b/source/android/app/src/main/res/layout/fragment_xhs_home_layout.xml @@ -30,6 +30,32 @@ + + + + + + + + + android:layout_height="wrap_content"> Media Player Setting pref.using_media_codec Use MediaCodec Decode - Default, software decoding is used. opening, hardware decoder used" + Default, MediaCodec decoding is used. opening, soft decoder used pref.using_android_media_player Use System Media Player @@ -55,7 +55,8 @@ Home Settings Demo Of Video Feed Page - Play URL/JSON + URL/JSON + LIVE URL Play Local Resource Online Samples Pip Samples @@ -70,6 +71,10 @@ Clear Play + Demo of Live URL Page + Live URL + Input live url to play + All Playing Speed Show/Hide diff --git a/source/android/app/src/main/res/xml/settings.xml b/source/android/app/src/main/res/xml/settings.xml index 3f9a721..55d8510 100644 --- a/source/android/app/src/main/res/xml/settings.xml +++ b/source/android/app/src/main/res/xml/settings.xml @@ -19,7 +19,7 @@ Int { - return 3 + return 4 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -324,12 +324,15 @@ extension MainViewController: UITableViewDataSource { switch indexPath.row { case 0: - cell.iconImageView.image = UIImage(named: "link")?.withRenderingMode(.alwaysTemplate) - cell.titleLabel.text = "Play URL / Json" + cell.iconImageView.image = UIImage(named: "vod_link")?.withRenderingMode(.alwaysTemplate) + cell.titleLabel.text = "VOD URL / Json" case 1: + cell.iconImageView.image = UIImage(named: "live_link")?.withRenderingMode(.alwaysTemplate) + cell.titleLabel.text = "LIVE URL" + case 2: cell.iconImageView.image = UIImage(named: "album")?.withRenderingMode(.alwaysTemplate) cell.titleLabel.text = "Play Local Resources" - case 2: + case 3: cell.iconImageView.image = UIImage(named: "flow_icon")?.withRenderingMode(.alwaysTemplate) cell.titleLabel.text = "Video Samples" default: @@ -352,10 +355,12 @@ extension MainViewController: UITableViewDelegate { switch indexPath.row { case 0: - playURLResource() + playURLResource(isLive: false) case 1: - playLocalVideo() + playURLResource(isLive: true) case 2: + playLocalVideo() + case 3: showRedFlowViewController() default: break diff --git a/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedDemoPlayerViewController.h b/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedDemoPlayerViewController.h index c72756b..4c41546 100644 --- a/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedDemoPlayerViewController.h +++ b/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedDemoPlayerViewController.h @@ -17,6 +17,7 @@ typedef void(^DemoCloseBlock)(void); withTitle:(NSString *)title URL:(NSString *)url isJson:(BOOL)isJson + isLive:(BOOL)isLive playList:(NSArray *)playList playListIndex:(NSInteger)playListIndex completion:(void(^)(void))completion diff --git a/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedDemoPlayerViewController.m b/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedDemoPlayerViewController.m index 5dfa709..168bbc4 100644 --- a/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedDemoPlayerViewController.m +++ b/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedDemoPlayerViewController.m @@ -34,6 +34,7 @@ typedef NS_ENUM(NSUInteger, RedTipsPosition) { @interface RedDemoPlayerViewController () @property (nonatomic, strong) NSString *url; @property (nonatomic, assign) BOOL isJson; +@property (nonatomic, assign) BOOL isLive; @property (nonatomic, strong) NSArray *playList; @property (nonatomic, assign) NSInteger playListIndex; @property (nonatomic, assign) RedScalingMode scaleMode; @@ -88,13 +89,14 @@ typedef NS_ENUM(NSUInteger, RedTipsPosition) { withTitle:(NSString *)title URL:(NSString *)url isJson:(BOOL)isJson + isLive:(BOOL)isLive playList:(NSArray *)playList playListIndex:(NSInteger)playListIndex completion:(void (^)(void))completion closeBlock:(void (^)(void))closeBlock { [RedDemoPlayerViewController setupLogCallback]; - RedDemoPlayerViewController *demoPlayerVC = [[RedDemoPlayerViewController alloc] initWithURL:url isJson:isJson]; + RedDemoPlayerViewController *demoPlayerVC = [[RedDemoPlayerViewController alloc] initWithURL:url isJson:isJson isLive:isLive]; demoPlayerVC.playList = [playList copy]; demoPlayerVC.playListIndex = playListIndex; demoPlayerVC.modalPresentationStyle = UIModalPresentationFullScreen; @@ -103,11 +105,12 @@ typedef NS_ENUM(NSUInteger, RedTipsPosition) { demoPlayerVC.closeBlock = closeBlock; } -- (instancetype)initWithURL:(NSString *)url isJson:(BOOL)isJson { +- (instancetype)initWithURL:(NSString *)url isJson:(BOOL)isJson isLive:(BOOL)isLive { self = [super init]; if (self) { self.url = url; self.isJson = isJson; + self.isLive = isLive; } return self; } @@ -216,7 +219,9 @@ typedef NS_ENUM(NSUInteger, RedTipsPosition) { [self.player setMute:NO]; [self.player setEnableVTB:![RedPlayerSettings sharedInstance].softDecoder]; [self.player setVideoCacheDir:[RedMediaUtil cachePath]]; - + if (self.isLive) { + [self.player setLiveMode:YES]; // config live mode + } [self.view addSubview:self.player.view]; [self.view sendSubviewToBack:self.player.view]; [self installMovieNotificationObservers]; @@ -417,6 +422,9 @@ typedef NS_ENUM(NSUInteger, RedTipsPosition) { - (void)closeClick:(id)sender { [self shutdown]; [self stopLoading]; + [self.mediaControl removeFromSuperview]; + [self.mediaControl stopRefresh]; + self.mediaControl = nil; [self dismissViewControllerAnimated:NO completion:^{ if (self.closeBlock) { self.closeBlock(); @@ -498,7 +506,7 @@ typedef NS_ENUM(NSUInteger, RedTipsPosition) { self.mediaControl.delegatePlayer = nil; self.mediaControl = nil; } - self.mediaControl = [[RedMediaControl alloc] initWithFrame:CGRectZero viewController:self playList:[self.playList copy] isLoop:self.isLoop]; + self.mediaControl = [[RedMediaControl alloc] initWithFrame:CGRectZero viewController:self playList:[self.playList copy] isLoop:self.isLoop isLive:self.isLive]; self.mediaControl.playListIndex = _playListIndex; self.mediaControl.layer.cornerRadius = 10; self.mediaControl.layer.masksToBounds = YES; @@ -526,7 +534,6 @@ typedef NS_ENUM(NSUInteger, RedTipsPosition) { } - (void)hideMediaControl { - [self.mediaControl stopRefresh]; self.mediaControl.hidden = YES; self.closeButton.hidden = YES; self.scaleModeButton.hidden = YES; @@ -597,21 +604,25 @@ typedef NS_ENUM(NSUInteger, RedTipsPosition) { // MARK: Gestures - (void)addGestureControls { - // Long Press - 2x Playback Speed - UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; - [self.view addGestureRecognizer:longPress]; - - // Double Tap - UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)]; - doubleTap.numberOfTapsRequired = 2; - [self.view addGestureRecognizer:doubleTap]; + UITapGestureRecognizer *doubleTap = nil; + if (!self.isLive) { + // Long Press - 2x Playback Speed + UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; + [self.view addGestureRecognizer:longPress]; + + // Double Tap + doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)]; + doubleTap.numberOfTapsRequired = 2; + [self.view addGestureRecognizer:doubleTap]; + } // Single Tap - Show/Hide tips UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)]; singleTap.numberOfTapsRequired = 1; [self.view addGestureRecognizer:singleTap]; - - [singleTap requireGestureRecognizerToFail:doubleTap]; + if (doubleTap) { + [singleTap requireGestureRecognizerToFail:doubleTap]; + } // Pan - Volume/Brightness UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; @@ -962,5 +973,9 @@ typedef NS_ENUM(NSUInteger, RedTipsPosition) { [self performSelector:@selector(hideMediaControl) withObject:nil afterDelay:5]; } +- (void)mediaControlLiveRefresh { + [self shutdown]; + [self setupPlayer]; +} @end diff --git a/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedMediaControl.h b/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedMediaControl.h index 408651d..f515371 100644 --- a/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedMediaControl.h +++ b/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedMediaControl.h @@ -14,6 +14,7 @@ - (void)switchPrevious:(id)sender; - (void)switchNext:(id)sender; - (void)onClickMute:(id)sender; +- (void)mediaControlLiveRefresh; - (void)didSliderTouchUpInside; - (void)slideCancelMediaControlHide; - (void)slideContinueMediaControlHide; @@ -35,7 +36,8 @@ - (id)initWithFrame:(CGRect)frame viewController:(UIViewController *)viewController playList:(NSArray *)playList - isLoop:(BOOL)isLoop; + isLoop:(BOOL)isLoop + isLive:(BOOL)isLive; - (float)progressValue; - (void)updatePlayPauseBtn:(BOOL)isPlaying; - (void)updateMuteBtn:(BOOL)isMute; diff --git a/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedMediaControl.m b/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedMediaControl.m index 8fd44e0..a97ad95 100644 --- a/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedMediaControl.m +++ b/source/ios/RedPlayerDemo/RedPlayerDemo/Player/RedMediaControl.m @@ -26,10 +26,13 @@ @property (nonatomic, strong) UIButton *muteButton; @property (nonatomic, strong) UIButton *loopButton; -@property (nonatomic, assign) BOOL isLoop; @property (nonatomic, strong) UIButton *listButton; +@property (nonatomic, strong) UIButton *liveRefreshButton; + @property (nonatomic, strong) NSArray *playList; +@property (nonatomic, assign) BOOL isLoop; +@property (nonatomic, assign) BOOL isLive; @end @implementation RedMediaControl { @@ -45,7 +48,8 @@ - (id)initWithFrame:(CGRect)frame viewController:(UIViewController *)viewController playList:(NSArray *)playList - isLoop:(BOOL)isLoop { + isLoop:(BOOL)isLoop + isLive:(BOOL)isLive { self = [super initWithFrame:frame]; if (self) { _viewController = viewController; @@ -54,6 +58,7 @@ self.playListIndex = 0; self.playList = playList; self.isLoop = isLoop; + self.isLive = isLive; [self setupSubviews]; [self startRefresh]; @@ -71,56 +76,71 @@ #define kTimeLabelWidth 50 #define kTimeLabelHeight 30 - (void)setupSubviews { - if (!self.playButton) { - self.playButton = [[UIButton alloc] initWithFrame:CGRectZero]; - [self.playButton setImage:[UIImage imageNamed:@"btn_player_pause"] forState:UIControlStateNormal]; - [self.playButton addTarget:self action:@selector(playAction:) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:self.playButton]; - } - [self.playButton mas_makeConstraints:^(MASConstraintMaker *make) { - make.centerX.centerY.mas_equalTo(self); - make.width.height.mas_equalTo(kPlayButtonWidth); - }]; - - if (self.playList.count > 0) { - if (!self.preButton) { - self.preButton = [[UIButton alloc] initWithFrame:CGRectZero]; - [self.preButton setImage:[UIImage imageNamed:@"icon_pre"] forState:UIControlStateNormal]; - [self.preButton addTarget:self action:@selector(preAction:) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:self.preButton]; + if (!_isLive) { + if (!self.playButton) { + self.playButton = [[UIButton alloc] initWithFrame:CGRectZero]; + [self.playButton setImage:[UIImage imageNamed:@"btn_player_pause"] forState:UIControlStateNormal]; + [self.playButton addTarget:self action:@selector(playAction:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:self.playButton]; } - self.preButton.enabled = (self.playListIndex > 0); - [self.preButton mas_makeConstraints:^(MASConstraintMaker *make) { - make.centerY.mas_equalTo(self); - make.right.mas_equalTo(self.playButton.mas_left).offset(-20); + [self.playButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.centerY.mas_equalTo(self); make.width.height.mas_equalTo(kPlayButtonWidth); }]; - if (!self.nextButton) { - self.nextButton = [[UIButton alloc] initWithFrame:CGRectZero]; - [self.nextButton setImage:[UIImage imageNamed:@"icon_next"] forState:UIControlStateNormal]; - [self.nextButton addTarget:self action:@selector(nextAction:) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:self.nextButton]; + + if (self.playList.count > 0) { + if (!self.preButton) { + self.preButton = [[UIButton alloc] initWithFrame:CGRectZero]; + [self.preButton setImage:[UIImage imageNamed:@"icon_pre"] forState:UIControlStateNormal]; + [self.preButton addTarget:self action:@selector(preAction:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:self.preButton]; + } + self.preButton.enabled = (self.playListIndex > 0); + [self.preButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.mas_equalTo(self); + make.right.mas_equalTo(self.playButton.mas_left).offset(-20); + make.width.height.mas_equalTo(kPlayButtonWidth); + }]; + if (!self.nextButton) { + self.nextButton = [[UIButton alloc] initWithFrame:CGRectZero]; + [self.nextButton setImage:[UIImage imageNamed:@"icon_next"] forState:UIControlStateNormal]; + [self.nextButton addTarget:self action:@selector(nextAction:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:self.nextButton]; + } + self.nextButton.enabled = (self.playListIndex < self.playList.count - 1); + [self.nextButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.mas_equalTo(self); + make.left.mas_equalTo(self.playButton.mas_right).offset(20); + make.width.height.mas_equalTo(kPlayButtonWidth); + }]; } - self.nextButton.enabled = (self.playListIndex < self.playList.count - 1); - [self.nextButton mas_makeConstraints:^(MASConstraintMaker *make) { - make.centerY.mas_equalTo(self); - make.left.mas_equalTo(self.playButton.mas_right).offset(20); + } else { // Live refresh + if (!self.liveRefreshButton) { + self.liveRefreshButton = [[UIButton alloc] initWithFrame:CGRectZero]; + [self.liveRefreshButton setImage:[UIImage imageNamed:@"icon_refresh"] forState:UIControlStateNormal]; + [self.liveRefreshButton addTarget:self action:@selector(refreshButtonClick:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:self.liveRefreshButton]; + } + [self.liveRefreshButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerX.centerY.mas_equalTo(self); make.width.height.mas_equalTo(kPlayButtonWidth); }]; } - if (!self.mediaProgressSlider) { self.mediaProgressSlider = [[UISlider alloc] initWithFrame:CGRectZero]; self.mediaProgressSlider.maximumTrackTintColor = UIColor.grayColor; self.mediaProgressSlider.tintColor = UIColor.redColor; [self.mediaProgressSlider setThumbImage:[UIImage imageNamed:@"slider"] forState:UIControlStateNormal]; [self addSubview:self.mediaProgressSlider]; - - [self.mediaProgressSlider addTarget:self action:@selector(slideTouchDown) forControlEvents:UIControlEventTouchDown]; - [self.mediaProgressSlider addTarget:self action:@selector(slideTouchUpInside) forControlEvents:UIControlEventTouchUpInside]; - [self.mediaProgressSlider addTarget:self action:@selector(slideValueChanged) forControlEvents:UIControlEventValueChanged]; - [self.mediaProgressSlider addTarget:self action:@selector(slideTouchUpOutside) forControlEvents:UIControlEventTouchUpOutside]; - [self.mediaProgressSlider addTarget:self action:@selector(slideTouchCancel) forControlEvents:UIControlEventTouchCancel]; + if (!_isLive) { + [self.mediaProgressSlider addTarget:self action:@selector(slideTouchDown) forControlEvents:UIControlEventTouchDown]; + [self.mediaProgressSlider addTarget:self action:@selector(slideTouchUpInside) forControlEvents:UIControlEventTouchUpInside]; + [self.mediaProgressSlider addTarget:self action:@selector(slideValueChanged) forControlEvents:UIControlEventValueChanged]; + [self.mediaProgressSlider addTarget:self action:@selector(slideTouchUpOutside) forControlEvents:UIControlEventTouchUpOutside]; + [self.mediaProgressSlider addTarget:self action:@selector(slideTouchCancel) forControlEvents:UIControlEventTouchCancel]; + } else { + self.mediaProgressSlider.userInteractionEnabled = NO; + } } [self.mediaProgressSlider mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self); @@ -184,56 +204,61 @@ make.height.mas_equalTo(30); }]; - - if (!self.listButton) { - self.listButton = [[UIButton alloc] initWithFrame:CGRectZero]; - self.listButton.titleLabel.textAlignment = NSTextAlignmentCenter; - [self.listButton setImage:[UIImage imageNamed:@"list"] forState:UIControlStateNormal]; - [self.listButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; - if (self.playList.count == 0) { - self.listButton.enabled = NO; + if (!_isLive) { + if (!self.listButton) { + self.listButton = [[UIButton alloc] initWithFrame:CGRectZero]; + self.listButton.titleLabel.textAlignment = NSTextAlignmentCenter; + [self.listButton setImage:[UIImage imageNamed:@"list"] forState:UIControlStateNormal]; + if (self.playList.count == 0) { + self.listButton.enabled = NO; + } + [self.listButton addTarget:self action:@selector(listButtonClick:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:self.listButton]; } - [self.listButton addTarget:self action:@selector(listButtonClick:) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:self.listButton]; - } - [self.listButton mas_makeConstraints:^(MASConstraintMaker *make) { - make.right.mas_equalTo(self).offset(-kSliderMargin); - make.centerY.mas_equalTo(self.hudButton); - make.width.mas_equalTo(20); - make.height.mas_equalTo(20); - }]; + [self.listButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.mas_equalTo(self).offset(-kSliderMargin); + make.centerY.mas_equalTo(self.hudButton); + make.width.mas_equalTo(20); + make.height.mas_equalTo(20); + }]; - if (!self.loopButton) { - self.loopButton = [[UIButton alloc] initWithFrame:CGRectZero]; - self.loopButton.titleLabel.textAlignment = NSTextAlignmentCenter; - [self.loopButton setImage:[UIImage imageNamed:self.isLoop ? @"loop" : @"sequence"] forState:UIControlStateNormal]; - [self.loopButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; - [self.loopButton addTarget:self action:@selector(loopButtonClick:) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:self.loopButton]; + if (!self.loopButton) { + self.loopButton = [[UIButton alloc] initWithFrame:CGRectZero]; + self.loopButton.titleLabel.textAlignment = NSTextAlignmentCenter; + [self.loopButton setImage:[UIImage imageNamed:self.isLoop ? @"loop" : @"sequence"] forState:UIControlStateNormal]; + [self.loopButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; + [self.loopButton addTarget:self action:@selector(loopButtonClick:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:self.loopButton]; + } + [self.loopButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.mas_equalTo(self.listButton.mas_left).offset(-20); + make.centerY.mas_equalTo(self.hudButton); + make.width.mas_equalTo(20); + make.height.mas_equalTo(20); + }]; + + if (!self.speedButton) { + self.speedButton = [[UIButton alloc] initWithFrame:CGRectZero]; + self.speedButton.titleLabel.textAlignment = NSTextAlignmentCenter; + [self.speedButton setTitle:@"1.0x" forState:UIControlStateNormal]; + [self.speedButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; + self.speedButton.titleLabel.font = [UIFont boldSystemFontOfSize:18]; + [self.speedButton addTarget:self action:@selector(speedButtonClick:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:self.speedButton]; + } + [self.speedButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.mas_equalTo(self.loopButton.mas_left).offset(-10); + make.centerY.mas_equalTo(self.hudButton); + make.width.mas_equalTo(50); + make.height.mas_equalTo(30); + }]; } - [self.loopButton mas_makeConstraints:^(MASConstraintMaker *make) { - make.right.mas_equalTo(self.listButton.mas_left).offset(-20); - make.centerY.mas_equalTo(self.hudButton); - make.width.mas_equalTo(20); - make.height.mas_equalTo(20); - }]; - - if (!self.speedButton) { - self.speedButton = [[UIButton alloc] initWithFrame:CGRectZero]; - self.speedButton.titleLabel.textAlignment = NSTextAlignmentCenter; - [self.speedButton setTitle:@"1.0x" forState:UIControlStateNormal]; - [self.speedButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; - self.speedButton.titleLabel.font = [UIFont boldSystemFontOfSize:18]; - [self.speedButton addTarget:self action:@selector(speedButtonClick:) forControlEvents:UIControlEventTouchUpInside]; - [self addSubview:self.speedButton]; - } - [self.speedButton mas_makeConstraints:^(MASConstraintMaker *make) { - make.right.mas_equalTo(self.loopButton.mas_left).offset(-10); - make.centerY.mas_equalTo(self.hudButton); - make.width.mas_equalTo(50); - make.height.mas_equalTo(30); - }]; +} +- (void)refreshButtonClick:(UIButton *)sender { + if ([self.delegatePlayer respondsToSelector:@selector(mediaControlLiveRefresh)]) { + [self.delegatePlayer mediaControlLiveRefresh]; + } } - (void)loopButtonClick:(UIButton *)sender { diff --git a/source/ios/RedPlayerDemo/RedPlayerDemo/RedAlertView.swift b/source/ios/RedPlayerDemo/RedPlayerDemo/RedAlertView.swift index 3192808..f2e2e38 100644 --- a/source/ios/RedPlayerDemo/RedPlayerDemo/RedAlertView.swift +++ b/source/ios/RedPlayerDemo/RedPlayerDemo/RedAlertView.swift @@ -8,19 +8,18 @@ import UIKit class RedAlertView: UIView { - let urlDefault = "aHR0cDovL3Nucy12aWRlby1hbC54aHNjZG4uY29tL3NwZWN0cnVtLzAxZTI5NjRlNDE2YmUwNzcwMTgzNzAwMzgxMWI0ZTM5YzlfNTEzLm1wNA==" + let urlVodDefault = "aHR0cDovL3Nucy12aWRlby1hbC54aHNjZG4uY29tL3NwZWN0cnVtLzAxZTI5NjRlNDE2YmUwNzcwMTgzNzAwMzgxMWI0ZTM5YzlfNTEzLm1wNA==" + let urlLiveDefault = "aHR0cDovL2xpdmUtcGxheS54aHNjZG4uY29tL2xpdmUvNTY5MDcxNDQ3Mjc2ODQwMjY4LmZsdg==" let jsonDefault = "ewogICAgInN0cmVhbSI6IHsKICAgICAgICAiYXYxIjogW10sCiAgICAgICAgImgyNjUiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJtYXN0ZXJfdXJsIjogImh0dHA6Ly9zbnMtdmlkZW8tYmQueGhzY2RuLmNvbS9zdHJlYW0vMTEwLzgvMDFlNTgzY2I2ZTBmZWQ1YTAxMDM3MDAzOGM4YWQ2NTM3OV84Lm1wNCIsCiAgICAgICAgICAgICAgICAid2VpZ2h0IjogNjIsCiAgICAgICAgICAgICAgICAiYmFja3VwX3VybHMiOiBbCiAgICAgICAgICAgICAgICAgICAgImh0dHA6Ly9zbnMtdmlkZW8tcWMueGhzY2RuLmNvbS9zdHJlYW0vMTEwLzgvMDFlNTgzY2I2ZTBmZWQ1YTAxMDM3MDAzOGM4YWQ2NTM3OV84Lm1wND9zaWduPTgxMzQyZGQwZGM1YjViYmViY2I0N2NmYTVmNzQ1YmY1JnQ9NjViNTI3NTQiLAogICAgICAgICAgICAgICAgICAgICJodHRwOi8vc25zLXZpZGVvLWh3Lnhoc2Nkbi5jb20vc3RyZWFtLzExMC84LzAxZTU4M2NiNmUwZmVkNWEwMTAzNzAwMzhjOGFkNjUzNzlfOC5tcDQiLAogICAgICAgICAgICAgICAgICAgICJodHRwOi8vc25zLXZpZGVvLWFsLnhoc2Nkbi5jb20vc3RyZWFtLzExMC84LzAxZTU4M2NiNmUwZmVkNWEwMTAzNzAwMzhjOGFkNjUzNzlfOC5tcDQiLAogICAgICAgICAgICAgICAgICAgICJodHRwOi8vc25zLXZpZGVvLWh3Lnhoc2Nkbi5uZXQvc3RyZWFtLzExMC84LzAxZTU4M2NiNmUwZmVkNWEwMTAzNzAwMzhjOGFkNjUzNzlfOC5tcDQiCiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgImhlaWdodCI6IDk2MCwKICAgICAgICAgICAgICAgICJ3aWR0aCI6IDcyMCwKICAgICAgICAgICAgICAgICJhdmdfYml0cmF0ZSI6IDM4MDYzNAogICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAiaDI2NCI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1hc3Rlcl91cmwiOiAiaHR0cDovL3Nucy12aWRlby1iZC54aHNjZG4uY29tL3N0cmVhbS8xMTAvMjU4LzAxZTU4M2NiNmUwZmVkNWEwMTAzNzAwMzhjOTVmODg0MmJfMjU4Lm1wNCIsCiAgICAgICAgICAgICAgICAid2VpZ2h0IjogNjIsCiAgICAgICAgICAgICAgICAiYmFja3VwX3VybHMiOiBbCiAgICAgICAgICAgICAgICAgICAgImh0dHA6Ly9zbnMtdmlkZW8tcWMueGhzY2RuLmNvbS9zdHJlYW0vMTEwLzI1OC8wMWU1ODNjYjZlMGZlZDVhMDEwMzcwMDM4Yzk1Zjg4NDJiXzI1OC5tcDQ/c2lnbj0yOGUxMzk3OWE2MGJhMjRiODk4ZGE0NzEyM2M5NmRkMiZ0PTY1YjUyNzU0IiwKICAgICAgICAgICAgICAgICAgICAiaHR0cDovL3Nucy12aWRlby1ody54aHNjZG4uY29tL3N0cmVhbS8xMTAvMjU4LzAxZTU4M2NiNmUwZmVkNWEwMTAzNzAwMzhjOTVmODg0MmJfMjU4Lm1wNCIsCiAgICAgICAgICAgICAgICAgICAgImh0dHA6Ly9zbnMtdmlkZW8tYWwueGhzY2RuLmNvbS9zdHJlYW0vMTEwLzI1OC8wMWU1ODNjYjZlMGZlZDVhMDEwMzcwMDM4Yzk1Zjg4NDJiXzI1OC5tcDQiLAogICAgICAgICAgICAgICAgICAgICJodHRwOi8vc25zLXZpZGVvLWh3Lnhoc2Nkbi5uZXQvc3RyZWFtLzExMC8yNTgvMDFlNTgzY2I2ZTBmZWQ1YTAxMDM3MDAzOGM5NWY4ODQyYl8yNTgubXA0IgogICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICJoZWlnaHQiOiA5NjAsCiAgICAgICAgICAgICAgICAid2lkdGgiOiA3MjAsCiAgICAgICAgICAgICAgICAiYXZnX2JpdHJhdGUiOiA1ODA4MTQKICAgICAgICAgICAgfQogICAgICAgIF0KICAgIH0KfQ==" var playButtonTapped: ((String, Bool) -> Void)? var closeButtonTapped: (() -> Void)? - + var isLive = false let titleLabel: UILabel = { let label = UILabel() label.font = .boldSystemFont(ofSize: 18) - label.text = "Enter URL or JSON" label.textAlignment = .center - label.heightAnchor.constraint(equalToConstant: 20).isActive = true return label - }() + }() let urlTextView: UITextView = { let textView = UITextView() @@ -29,7 +28,6 @@ class RedAlertView: UIView { textView.layer.borderWidth = 1 textView.layer.borderColor = UIColor.gray.cgColor textView.layer.cornerRadius = 5 - textView.heightAnchor.constraint(equalToConstant: 60).isActive = true return textView }() @@ -41,7 +39,6 @@ class RedAlertView: UIView { textView.layer.borderWidth = 1 textView.layer.borderColor = UIColor.gray.cgColor textView.layer.cornerRadius = 5 - textView.heightAnchor.constraint(equalToConstant: 140).isActive = true return textView }() @@ -94,41 +91,52 @@ class RedAlertView: UIView { return button } - override init(frame: CGRect) { + public init(frame: CGRect, isLive: Bool) { super.init(frame: frame) - + self.isLive = isLive self.backgroundColor = .systemBackground self.layer.cornerRadius = 10 self.layer.shadowColor = UIColor.lightGray.cgColor self.layer.shadowOffset = CGSize(width: 0, height: 1) self.layer.shadowOpacity = 0.3 self.layer.shadowRadius = 4.0 - let stackViewH1 = UIStackView(arrangedSubviews: [urlButton, jsonButton, clearButton]) + let stackViewH1 = UIStackView(arrangedSubviews: isLive ? [urlButton, clearButton] : [urlButton, jsonButton, clearButton]) + let stackViewH2 = UIStackView(arrangedSubviews: [closeButton, playButton]) + let stackView = UIStackView(arrangedSubviews: isLive ? [titleLabel, urlTextView, stackViewH1, stackViewH2] : [titleLabel, urlTextView, jsonTextView, stackViewH1, stackViewH2]) + stackViewH1.axis = .horizontal stackViewH1.distribution = .fillEqually stackViewH1.spacing = 10 stackViewH1.translatesAutoresizingMaskIntoConstraints = false - let stackViewH2 = UIStackView(arrangedSubviews: [closeButton, playButton]) stackViewH2.axis = .horizontal stackViewH2.distribution = .fillEqually stackViewH2.spacing = 10 stackViewH2.translatesAutoresizingMaskIntoConstraints = false - let stackView = UIStackView(arrangedSubviews: [titleLabel, urlTextView, jsonTextView, stackViewH1, stackViewH2]) stackView.axis = .vertical stackView.distribution = .fillProportionally stackView.spacing = 10 stackView.translatesAutoresizingMaskIntoConstraints = false self.addSubview(stackView) + if isLive { + titleLabel.text = "Enter Live URL" + urlTextView.heightAnchor.constraint(equalToConstant: 200).isActive = true + } else { + titleLabel.text = "Enter VOD URL or JSON" + urlTextView.heightAnchor.constraint(equalToConstant: 60).isActive = true + jsonTextView.heightAnchor.constraint(equalToConstant: 140).isActive = true + } + titleLabel.heightAnchor.constraint(equalToConstant: 20).isActive = true + NSLayoutConstraint.activate([ stackView.topAnchor.constraint(equalTo: self.topAnchor, constant: 10), stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10), stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10), stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10) ]) - + urlTextView.delegate = self jsonTextView.delegate = self urlButton.addTarget(self, action: #selector(urlButtonAction), for: .touchUpInside) @@ -147,7 +155,7 @@ class RedAlertView: UIView { @objc func urlButtonAction() { disableInput(textView: jsonTextView, content: "Please clear the JSON field first") - enableInput(textView: urlTextView, content: RedMediaUtil.decodeBase64(base64EncodedString: urlDefault) ?? "") + enableInput(textView: urlTextView, content: RedMediaUtil.decodeBase64(base64EncodedString: isLive ? urlLiveDefault : urlVodDefault) ?? "") } @objc func jsonButtonAction() { diff --git a/source/ios/RedPlayerDemo/RedPlayerDemo/RedFlowViewController.swift b/source/ios/RedPlayerDemo/RedPlayerDemo/RedFlowViewController.swift index 029d578..f33cd3f 100644 --- a/source/ios/RedPlayerDemo/RedPlayerDemo/RedFlowViewController.swift +++ b/source/ios/RedPlayerDemo/RedPlayerDemo/RedFlowViewController.swift @@ -245,7 +245,7 @@ open class RedFlowViewController: UIViewController, RedFlowLayoutDelegate, UICol videoDictArray.append(videoDict) } - RedDemoPlayerViewController.present(from: self, withTitle: "", url: playUrl, isJson: item.isJson, playList: videoDictArray, playListIndex: indexPath.item, completion: nil) { + RedDemoPlayerViewController.present(from: self, withTitle: "", url: playUrl, isJson: item.isJson, isLive: false, playList: videoDictArray, playListIndex: indexPath.item, completion: nil) { self.playingCell = nil self.scrollViewEndDecelerating() }