Custom video player layer to avoid "double present/dismiss"

// FREEBIE
pull/1/head
Michael Kirk 7 years ago
parent 918e3f7dfe
commit 86d61eee30

@ -160,6 +160,7 @@
452EA09E1EA7ABE00078744B /* AttachmentPointerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */; };
452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; };
452ECA4E1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; };
453034AB200289F50018945D /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 453034AA200289F50018945D /* VideoPlayerView.swift */; };
45360B8D1F9521F800FA666C /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45360B8C1F9521F800FA666C /* Searcher.swift */; };
45360B8E1F9521F800FA666C /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45360B8C1F9521F800FA666C /* Searcher.swift */; };
45360B901F9527DA00FA666C /* SearcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45360B8F1F9527DA00FA666C /* SearcherTest.swift */; };
@ -633,6 +634,7 @@
452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MesssagesBubblesSizeCalculatorTest.swift; path = Models/MesssagesBubblesSizeCalculatorTest.swift; sourceTree = "<group>"; };
452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentPointerView.swift; sourceTree = "<group>"; };
452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MessageFetcherJob.swift; path = Jobs/MessageFetcherJob.swift; sourceTree = "<group>"; };
453034AA200289F50018945D /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = "<group>"; };
45360B8C1F9521F800FA666C /* Searcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Searcher.swift; sourceTree = "<group>"; };
45360B8F1F9527DA00FA666C /* SearcherTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearcherTest.swift; sourceTree = "<group>"; };
45387B021E36D650005D00B3 /* OWS102MoveLoggingPreferenceToUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWS102MoveLoggingPreferenceToUserDefaults.h; path = Migrations/OWS102MoveLoggingPreferenceToUserDefaults.h; sourceTree = "<group>"; };
@ -1507,6 +1509,7 @@
76EB052B18170B33006006FC /* Views */ = {
isa = PBXGroup;
children = (
453034AA200289F50018945D /* VideoPlayerView.swift */,
45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */,
452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */,
34E3E5671EC4B19400495BAC /* AudioProgressView.swift */,
@ -2327,6 +2330,7 @@
45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */,
45638BDF1F3DDB2200128435 /* MessageSender+Promise.swift in Sources */,
34535D821E256BE9008A4747 /* UIView+OWS.m in Sources */,
453034AB200289F50018945D /* VideoPlayerView.swift in Sources */,
45F3AEB61DFDE7900080CE33 /* AvatarImageView.swift in Sources */,
7038632718F70C0700D4A43F /* CryptoTools.m in Sources */,
45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */,

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "VideoPlayer_Slider_Thumb_15x15_@1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "VideoPlayer_Slider_Thumb_15x15_@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "VideoPlayer_Slider_Thumb_15x15_@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -18,7 +18,6 @@
NS_ASSUME_NONNULL_BEGIN
#define kMinZoomScale 1.0f
#define kMaxZoomScale 8.0f
// In order to use UIMenuController, the view from which it is
@ -45,10 +44,11 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@interface FullImageViewController () <UIScrollViewDelegate, UIGestureRecognizerDelegate>
@interface FullImageViewController () <UIScrollViewDelegate, UIGestureRecognizerDelegate, PlayerProgressBarDelegate>
@property (nonatomic) UIScrollView *scrollView;
@property (nonatomic) UIImageView *imageView;
//@property (nonatomic) UIImageView *imageView;
@property (nonatomic) UIView *imageView;
@property (nonatomic) UIButton *shareButton;
@ -63,6 +63,10 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) BOOL areToolbarsHidden;
@property (nonatomic, nullable) MPMoviePlayerController *mpVideoPlayer;
@property (nonatomic, nullable) AVPlayer *videoPlayer;
@property (nonatomic, nullable) UIButton *playVideoButton;
@property (nonatomic, nullable) PlayerProgressBar *videoProgressBar;
@property (nonatomic, nullable) UIBarButtonItem *videoPlayBarButton;
@property (nonatomic, nullable) UIBarButtonItem *videoPauseBarButton;
@property (nonatomic, nullable) NSArray<NSLayoutConstraint *> *imageViewConstraints;
@property (nonatomic, nullable) NSLayoutConstraint *imageViewBottomConstraint;
@ -201,7 +205,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)updateMinZoomScale
{
CGSize viewSize = self.scrollView.bounds.size;
UIImage *image = self.imageView.image;
UIImage *image = self.image;
OWSAssert(image);
if (image.size.width == 0 || image.size.height == 0) {
@ -212,8 +216,11 @@ NS_ASSUME_NONNULL_BEGIN
CGFloat scaleWidth = viewSize.width / image.size.width;
CGFloat scaleHeight = viewSize.height / image.size.height;
CGFloat minScale = MIN(scaleWidth, scaleHeight);
self.scrollView.minimumZoomScale = minScale;
self.scrollView.zoomScale = minScale;
if (minScale != self.scrollView.minimumZoomScale) {
self.scrollView.minimumZoomScale = minScale;
self.scrollView.zoomScale = minScale;
}
}
#pragma mark - Initializers
@ -246,12 +253,7 @@ NS_ASSUME_NONNULL_BEGIN
self.imageView = [UIImageView new];
}
} else if (self.isVideo) {
[self setupVideoPlayer];
// Present the static video preview
UIImageView *imageView = [[UIImageView alloc] initWithImage:self.image];
self.imageView = imageView;
self.imageView = [self buildVideoPlayerView];
} else {
// Present the static image using standard UIImageView
UIImageView *imageView = [[UIImageView alloc] initWithImage:self.image];
@ -276,34 +278,43 @@ NS_ASSUME_NONNULL_BEGIN
[self applyInitialImageViewConstraints];
if (self.isVideo) {
UIButton *playButton = [UIButton new];
PlayerProgressBar *videoProgressBar = [PlayerProgressBar new];
videoProgressBar.delegate = self;
videoProgressBar.player = self.videoPlayer;
self.videoProgressBar = videoProgressBar;
[self.view addSubview:videoProgressBar];
[videoProgressBar autoPinWidthToSuperview];
[videoProgressBar autoPinToTopLayoutGuideOfViewController:self withInset:0];
CGFloat kVideoProgressBarHeight = 44;
[videoProgressBar autoSetDimension:ALDimensionHeight toSize:kVideoProgressBarHeight];
UIButton *playVideoButton = [UIButton new];
self.playVideoButton = playVideoButton;
[playButton addTarget:self action:@selector(playVideo) forControlEvents:UIControlEventTouchUpInside];
[playVideoButton addTarget:self action:@selector(playVideo) forControlEvents:UIControlEventTouchUpInside];
UIImage *playImage = [UIImage imageNamed:@"play_button"];
[playButton setBackgroundImage:playImage forState:UIControlStateNormal];
playButton.contentMode = UIViewContentModeScaleAspectFill;
[playVideoButton setBackgroundImage:playImage forState:UIControlStateNormal];
playVideoButton.contentMode = UIViewContentModeScaleAspectFill;
[self.view addSubview:playButton];
[self.view addSubview:playVideoButton];
CGFloat playButtonWidth = ScaleFromIPhone5(70);
[playButton autoSetDimensionsToSize:CGSizeMake(playButtonWidth, playButtonWidth)];
[playButton autoCenterInSuperview];
CGFloat playVideoButtonWidth = ScaleFromIPhone5(70);
[playVideoButton autoSetDimensionsToSize:CGSizeMake(playVideoButtonWidth, playVideoButtonWidth)];
[playVideoButton autoCenterInSuperview];
}
UIToolbar *footerBar = [UIToolbar new];
_footerBar = footerBar;
footerBar.barTintColor = [UIColor ows_signalBrandBlueColor];
[footerBar setItems:@[
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
target:self
action:@selector(didPressShare:)],
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash
target:self
action:@selector(didPressDelete:)],
]
animated:NO];
self.videoPlayBarButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay
target:self
action:@selector(didPressPlayBarButton:)];
self.videoPauseBarButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPause
target:self
action:@selector(didPressPauseBarButton:)];
[self updateFooterBarButtonItemsWithIsPlayingVideo:YES];
[self.view addSubview:footerBar];
[footerBar autoPinWidthToSuperview];
@ -311,6 +322,36 @@ NS_ASSUME_NONNULL_BEGIN
[footerBar autoSetDimension:ALDimensionHeight toSize:kFooterHeight];
}
- (void)updateFooterBarButtonItemsWithIsPlayingVideo:(BOOL)isPlayingVideo
{
OWSAssert(self.footerBar);
NSMutableArray<UIBarButtonItem *> *toolbarItems = [NSMutableArray new];
[toolbarItems addObjectsFromArray:@[
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
target:self
action:@selector(didPressShare:)],
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil],
]];
if (self.isVideo) {
UIBarButtonItem *playerButton = isPlayingVideo ? self.videoPauseBarButton : self.videoPlayBarButton;
[toolbarItems addObjectsFromArray:@[
playerButton,
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:nil],
]];
}
[toolbarItems addObject:[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash
target:self
action:@selector(didPressDelete:)]];
[self.footerBar setItems:toolbarItems animated:NO];
}
- (void)applyInitialImageViewConstraints
{
if (self.imageViewConstraints.count > 0) {
@ -352,7 +393,7 @@ NS_ASSUME_NONNULL_BEGIN
]];
}
- (void)setupVideoPlayer
- (UIView *)buildVideoPlayerView
{
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:[self.attachmentUrl path]]) {
@ -361,12 +402,23 @@ NS_ASSUME_NONNULL_BEGIN
if (@available(iOS 9.0, *)) {
AVPlayer *player = [[AVPlayer alloc] initWithURL:self.attachmentUrl];
[player seekToTime:kCMTimeZero];
self.videoPlayer = player;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidPlayToCompletion:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:player.currentItem];
VideoPlayerView *playerView = [VideoPlayerView new];
playerView.player = player;
[NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow
forConstraints:^{
[playerView autoSetDimensionsToSize:self.image.size];
}];
return playerView;
} else {
MPMoviePlayerController *videoPlayer =
[[MPMoviePlayerController alloc] initWithContentURL:self.attachmentStream.mediaURL];
@ -375,6 +427,8 @@ NS_ASSUME_NONNULL_BEGIN
videoPlayer.controlStyle = MPMovieControlStyleNone;
[videoPlayer prepareToPlay];
return [[UIImageView alloc] initWithImage:self.image];
//
// [[NSNotificationCenter defaultCenter] addObserver:self
// selector:@selector(moviePlayerWillExitFullscreen:)
@ -426,31 +480,17 @@ NS_ASSUME_NONNULL_BEGIN
_areToolbarsHidden = areToolbarsHidden;
if (!areToolbarsHidden) {
// Hiding the status bar affects the positioing of the navbar. We don't want to show that in the animation
// so when *showing* the toolbars, we show the status bar first. When hiding, we hide it last.
[[UIApplication sharedApplication] setStatusBarHidden:areToolbarsHidden withAnimation:UIStatusBarAnimationFade];
}
// Hiding the status bar affects the positioing of the navbar. We don't want to show that in the animation
// so when *showing* the toolbars, we show the status bar first. When hiding, we hide it last.
[[UIApplication sharedApplication] setStatusBarHidden:areToolbarsHidden withAnimation:UIStatusBarAnimationNone];
[self.navigationController setNavigationBarHidden:areToolbarsHidden animated:NO];
self.videoProgressBar.hidden = areToolbarsHidden;
[UIView animateWithDuration:0.1
animations:^(void) {
self.view.backgroundColor = areToolbarsHidden ? UIColor.blackColor : UIColor.whiteColor;
self.navigationController.navigationBar.alpha = areToolbarsHidden ? 0 : 1;
self.footerBar.alpha = areToolbarsHidden ? 0 : 1;
}
completion:^(BOOL finished) {
// although navbar has 0 alpha at this point, if we don't also "hide" it, adjusting the status bar
// resets the alpha.
if (areToolbarsHidden) {
// [self.navigationController setNavigationBarHidden:areToolbarsHidden
// animated:NO];
// Hiding the status bar affects the positioing of the navbar. We don't want to show that in the
// animation so when *showing* the toolbars, we show the status bar first. When hiding, we hide it last.
[[UIApplication sharedApplication] setStatusBarHidden:areToolbarsHidden
withAnimation:UIStatusBarAnimationNone];
// position the navbar, but have it be transparent
self.navigationController.navigationBar.alpha = 0;
}
}];
animations:^(void) {
self.view.backgroundColor = areToolbarsHidden ? UIColor.blackColor : UIColor.whiteColor;
self.footerBar.alpha = areToolbarsHidden ? 0 : 1;
}];
}
- (void)initializeGestureRecognizers
@ -490,7 +530,6 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Gesture Recognizers
- (void)didTapDismissButton:(id)sender
{
[self dismiss];
@ -605,6 +644,20 @@ NS_ASSUME_NONNULL_BEGIN
[self didPressDelete:sender];
}
- (void)didPressPlayBarButton:(id)sender
{
OWSAssert(self.isVideo);
OWSAssert(self.videoPlayer);
[self playVideo];
}
- (void)didPressPauseBarButton:(id)sender
{
OWSAssert(self.isVideo);
OWSAssert(self.videoPlayer);
[self pauseVideo];
}
#pragma mark - Presentation
- (void)presentFromViewController:(UIViewController *)viewController
@ -725,23 +778,30 @@ NS_ASSUME_NONNULL_BEGIN
- (void)playVideo
{
OWSAssert(self.isVideo);
OWSAssert(self.videoPlayer);
AVPlayerViewController *vc = [AVPlayerViewController new];
AVPlayer *player = self.videoPlayer;
vc.player = player;
vc.modalPresentationStyle = UIModalPresentationCustom;
vc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self updateFooterBarButtonItemsWithIsPlayingVideo:YES];
self.playVideoButton.hidden = YES;
self.areToolbarsHidden = YES;
// Rewind for repeated plays
[player seekToTime:kCMTimeZero];
[self presentViewController:vc
animated:NO
completion:^(void) {
[player play];
}];
OWSAssert(player.currentItem);
AVPlayerItem *item = player.currentItem;
if (CMTIME_COMPARE_INLINE(item.currentTime, ==, item.duration)) {
// Rewind for repeated plays
[player seekToTime:kCMTimeZero];
}
[player play];
}
- (void)pauseVideo
{
OWSAssert(self.isVideo);
OWSAssert(self.videoPlayer);
[self updateFooterBarButtonItemsWithIsPlayingVideo:NO];
[self.videoPlayer pause];
}
- (void)playerItemDidPlayToCompletion:(NSNotification *)notification
@ -750,9 +810,40 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(self.videoPlayer);
DDLogVerbose(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
[self dismissViewControllerAnimated:NO completion:nil];
// [self dismissViewControllerAnimated:NO completion:nil];
self.areToolbarsHidden = NO;
self.playVideoButton.hidden = NO;
[self updateFooterBarButtonItemsWithIsPlayingVideo:NO];
}
- (void)playerProgressBarDidStartScrubbing:(PlayerProgressBar *)playerProgressBar
{
OWSAssert(self.videoPlayer);
[self.videoPlayer pause];
}
- (void)playerProgressBar:(PlayerProgressBar *)playerProgressBar scrubbedToTime:(CMTime)time
{
OWSAssert(self.videoPlayer);
[self.videoPlayer seekToTime:time];
}
- (void)playerProgressBar:(PlayerProgressBar *)playerProgressBar
didFinishScrubbingAtTime:(CMTime)time
shouldResumePlayback:(BOOL)shouldResumePlayback
{
OWSAssert(self.videoPlayer);
[self.videoPlayer seekToTime:time];
if (shouldResumePlayback) {
[self.videoPlayer play];
}
}
// iOS8 TODO
- (void)moviePlayerPlaybackStateDidChange:(NSNotification *)notification
{
DDLogDebug(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);

@ -0,0 +1,200 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
@available(iOS 9.0, *)
@objc
public class VideoPlayerView: UIView {
var player: AVPlayer? {
get {
return playerLayer.player
}
set {
playerLayer.player = newValue
}
}
var playerLayer: AVPlayerLayer {
return layer as! AVPlayerLayer
}
// Override UIView property
override public static var layerClass: AnyClass {
return AVPlayerLayer.self
}
}
@available(iOS 9.0, *)
@objc
public protocol PlayerProgressBarDelegate {
func playerProgressBarDidStartScrubbing(_ playerProgressBar: PlayerProgressBar)
func playerProgressBar(_ playerProgressBar: PlayerProgressBar, scrubbedToTime time: CMTime)
func playerProgressBar(_ playerProgressBar: PlayerProgressBar, didFinishScrubbingAtTime time: CMTime, shouldResumePlayback: Bool)
}
@available(iOS 9.0, *)
@objc
public class PlayerProgressBar: UIView {
public let TAG = "[PlayerProgressBar]"
@objc
public weak var delegate: PlayerProgressBarDelegate?
private lazy var formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.minute, .second ]
formatter.zeroFormattingBehavior = [ .pad ]
return formatter
}()
// MARK: Subviews
private let positionLabel = UILabel()
private let remainingLabel = UILabel()
private let slider = UISlider()
private let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
weak private var progressObserver: AnyObject?
private let kPreferredTimeScale: CMTimeScale = 100
public var player: AVPlayer? {
didSet {
guard let item = player?.currentItem else {
owsFail("No player item")
return
}
slider.minimumValue = 0
let duration: CMTime = item.asset.duration
slider.maximumValue = Float(CMTimeGetSeconds(duration))
// OPTIMIZE We need a high frequency observer for smooth slider updates,
// but could use a much less frequent observer for label updates
progressObserver = player?.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.01, preferredTimescale: kPreferredTimeScale), queue: nil, using: { [weak self] (_) in
self?.updateState()
}) as AnyObject
updateState()
}
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public init(frame: CGRect) {
super.init(frame: frame)
// Background
backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
if !UIAccessibilityIsReduceTransparencyEnabled() {
addSubview(blurEffectView)
blurEffectView.autoPinToSuperviewEdges()
}
// Configure controls
let kLabelFont = UIFont.monospacedDigitSystemFont(ofSize: 12, weight: UIFontWeightRegular)
positionLabel.font = kLabelFont
remainingLabel.font = kLabelFont
// We use a smaller thumb for the progress slider.
slider.setThumbImage(#imageLiteral(resourceName: "sliderProgressThumb"), for: .normal)
slider.maximumTrackTintColor = UIColor.ows_black()
slider.minimumTrackTintColor = UIColor.ows_black()
slider.addTarget(self, action: #selector(handleSliderTouchDown), for: .touchDown)
slider.addTarget(self, action: #selector(handleSliderTouchUp), for: .touchUpInside)
slider.addTarget(self, action: #selector(handleSliderTouchUp), for: .touchUpOutside)
slider.addTarget(self, action: #selector(handleSliderValueChanged), for: .valueChanged)
// Layout Subviews
addSubview(positionLabel)
addSubview(remainingLabel)
addSubview(slider)
positionLabel.autoPinEdge(toSuperviewMargin: .leading)
positionLabel.autoVCenterInSuperview()
let kSliderMargin: CGFloat = 8
slider.autoPinEdge(.leading, to: .trailing, of: positionLabel, withOffset: kSliderMargin)
slider.autoVCenterInSuperview()
remainingLabel.autoPinEdge(.leading, to: .trailing, of: slider, withOffset: kSliderMargin)
remainingLabel.autoPinEdge(toSuperviewMargin: .trailing)
remainingLabel.autoVCenterInSuperview()
}
// MARK: Gesture handling
var wasPlayingWhenScrubbingStarted: Bool = false
@objc
private func handleSliderTouchDown(_ slider: UISlider) {
guard let player = self.player else {
owsFail("player was nil")
return
}
self.wasPlayingWhenScrubbingStarted = (player.rate != 0) && (player.error == nil)
self.delegate?.playerProgressBarDidStartScrubbing(self)
}
@objc
private func handleSliderTouchUp(_ slider: UISlider) {
let sliderTime = time(slider: slider)
self.delegate?.playerProgressBar(self, didFinishScrubbingAtTime: sliderTime, shouldResumePlayback:wasPlayingWhenScrubbingStarted)
}
@objc
private func handleSliderValueChanged(_ slider: UISlider) {
let sliderTime = time(slider: slider)
self.delegate?.playerProgressBar(self, scrubbedToTime: sliderTime)
}
// MARK: Render cycle
private func updateState() {
guard let player = player else {
owsFail("\(TAG) player isn't set.")
return
}
guard let item = player.currentItem else {
owsFail("\(TAG) player has no item.")
return
}
let position = player.currentTime()
let positionSeconds: Float64 = CMTimeGetSeconds(position)
positionLabel.text = formatter.string(from: positionSeconds)
let duration: CMTime = item.asset.duration
let remainingTime = duration - position
let remainingSeconds = CMTimeGetSeconds(remainingTime)
guard let remainingString = formatter.string(from: remainingSeconds) else {
owsFail("unable to format time remaining")
remainingLabel.text = "0:00"
return
}
// show remaining time as negative
remainingLabel.text = "-\(remainingString)"
slider.setValue(Float(positionSeconds), animated: false)
}
// MARK: Util
private func time(slider: UISlider) -> CMTime {
let seconds: Double = Double(slider.value)
return CMTime(seconds: seconds, preferredTimescale: kPreferredTimeScale)
}
}
Loading…
Cancel
Save