Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.

Commit

Permalink
iOS - Add option in the video player for going backward & forward (#1354
Browse files Browse the repository at this point in the history
)

* LEARNER-7668, Add forward video button and update the buttons layout on video player screen

* LEARNER-7668, add seeking label animation

* LEARNER-7688, Fixed comments

* LEARNER-7688, Fixed comments

* LEARNER-7688, Fixed comments

* LEARNER-7688, Fixed comments

* LEARNER-7688, Fixed comment

* LEARNER-7688, Fixed comments

* small code improvement

* small code improvements

* refactor seek to handle rapid seeks

* code improvement

Co-authored-by: Saeed Bashir <[email protected]>
Co-authored-by: Muhammad Umer <[email protected]>
  • Loading branch information
3 people authored Apr 28, 2020
1 parent 9350c40 commit db2bcf8
Show file tree
Hide file tree
Showing 18 changed files with 208 additions and 49 deletions.
39 changes: 23 additions & 16 deletions Source/CutomePlayer/VideoPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ class VideoPlayer: UIViewController,VideoPlayerControlsDelegate,TranscriptManage
let loadingIndicatorView = UIActivityIndicatorView(style: .white)
private var lastElapsedTime: TimeInterval = 0
private var transcriptManager: TranscriptManager?
private let videoSkipBackwardsDuration: Double = 30
private var playerTimeBeforeSeek:TimeInterval = 0
private var playerState: PlayerState = .stoped
private var isObserverAdded: Bool = false
Expand Down Expand Up @@ -541,12 +540,12 @@ class VideoPlayer: UIViewController,VideoPlayerControlsDelegate,TranscriptManage
environment.interface?.sendAnalyticsEvents(.pause, withCurrentTime: currentTime, forVideo: video, playMedium: nil)
}
else {

guard let video = video else { return }

let playedInterval = environment.interface?.lastPlayedInterval(forVideo: video) ?? 0.0
let duration = Float(video.summary?.duration ?? 0)

// Ignore the last second of video because sometimes saved played time is 1 second less duration for a watched video
if duration > 0 && playedInterval + 1 >= duration {
// Replay the video
Expand All @@ -559,22 +558,27 @@ class VideoPlayer: UIViewController,VideoPlayerControlsDelegate,TranscriptManage
environment.interface?.sendAnalyticsEvents(.play, withCurrentTime: currentTime, forVideo: video, playMedium: nil)
}
}

func seekBackwardPressed(playerControls: VideoPlayerControls) {
func seekVideo(playerControls: VideoPlayerControls, skipDuration: Double, type: SeekType) {
let oldTime = currentTime
let videoDuration = CMTimeGetSeconds(duration)
let elapsedTime: Float64 = videoDuration * Float64(playerControls.durationSliderValue)
let backTime = elapsedTime > videoSkipBackwardsDuration ? elapsedTime - videoSkipBackwardsDuration : 0.0
playerControls.updateTimeLabel(elapsedTime: backTime, duration: videoDuration)
seek(to: backTime)
let requestedDuration = type == .rewind ? -skipDuration : skipDuration
let updatedElapseTime = elapsedTime + requestedDuration
let estimatedSliderValue = Float(updatedElapseTime / duration.seconds)
playerControls.durationSliderValue = estimatedSliderValue
playerControls.updateTimeLabel(elapsedTime: updatedElapseTime, duration: CMTimeGetSeconds(self.duration))
seek(to: elapsedTime + requestedDuration)

if let videoId = video?.summary?.videoID, let courseId = video?.course_id, let unitUrl = video?.summary?.unitURL {
environment.analytics.trackVideoSeekRewind(videoId, requestedDuration:-videoSkipBackwardsDuration, oldTime:oldTime, newTime: currentTime, courseID: courseId, unitURL: unitUrl, skipType: "skip")
if let videoId = video?.summary?.videoID,
let courseId = video?.course_id,
let unitUrl = video?.summary?.unitURL {
environment.analytics.trackVideoSeek(videoId, requestedDuration: requestedDuration, oldTime: oldTime, newTime: currentTime, courseID: courseId, unitURL: unitUrl, skipType: "skip")
}
}

func fullscreenPressed(playerControls: VideoPlayerControls) {
DispatchQueue.main.async {[weak self] in
DispatchQueue.main.async{ [weak self] in
if let weakSelf = self {
weakSelf.setFullscreen(fullscreen: !weakSelf.isFullScreen, animated: true, with: UIInterfaceOrientation.landscapeLeft, forceRotate:!weakSelf.isVerticallyCompact())
}
Expand All @@ -598,15 +602,18 @@ class VideoPlayer: UIViewController,VideoPlayerControlsDelegate,TranscriptManage
let elapsedTime: Float64 = videoDuration * Float64(playerControls.durationSliderValue)
playerControls.updateTimeLabel(elapsedTime: elapsedTime, duration: videoDuration)
seek(to: elapsedTime)
if let videoId = video?.summary?.videoID, let courseId = video?.course_id, let unitUrl = video?.summary?.unitURL {
environment.analytics.trackVideoSeekRewind(videoId, requestedDuration:currentTime - playerTimeBeforeSeek, oldTime:playerTimeBeforeSeek, newTime: currentTime, courseID: courseId, unitURL: unitUrl, skipType: "slide")
if let videoId = video?.summary?.videoID,
let courseId = video?.course_id,
let unitUrl = video?.summary?.unitURL {
environment.analytics.trackVideoSeek(videoId, requestedDuration:currentTime - playerTimeBeforeSeek, oldTime:playerTimeBeforeSeek, newTime: currentTime, courseID: courseId, unitURL: unitUrl, skipType: "slide")
}
}

func seek(to time: Double) {
if player.currentItem?.status != .readyToPlay { return }

player.currentItem?.seek(to: CMTimeMakeWithSeconds(time, preferredTimescale: preferredTimescale), toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) { [weak self]
let cmTime = CMTimeMakeWithSeconds(time, preferredTimescale: preferredTimescale)
player.currentItem?.seek(to: cmTime, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self]
(completed: Bool) -> Void in
if self?.playerState == .playing {
self?.controls?.autoHide()
Expand Down
122 changes: 107 additions & 15 deletions Source/CutomePlayer/VideoPlayerControls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
import UIKit
import CoreMedia

enum SeekType {
case rewind, forward
}

protocol VideoPlayerControlsDelegate: class {
func playPausePressed(playerControls: VideoPlayerControls, isPlaying: Bool)
func seekBackwardPressed(playerControls: VideoPlayerControls)
func seekVideo(playerControls: VideoPlayerControls, skipDuration: Double, type: SeekType)
func fullscreenPressed(playerControls: VideoPlayerControls)
func setPlayBackSpeed(playerControls: VideoPlayerControls, speed:OEXVideoSpeed)
func sliderValueChanged(playerControls: VideoPlayerControls)
Expand All @@ -27,18 +31,23 @@ class VideoPlayerControls: UIView, VideoPlayerSettingsDelegate {
private let environment : Environment
fileprivate var settings = VideoPlayerSettings()
private var isControlsHidden = true
private var isAnimating = false
fileprivate var subtitleActivated = false
private var bufferedTimer: Timer?
weak private var videoPlayer: VideoPlayer?
weak var delegate : VideoPlayerControlsDelegate?
private let previousButtonSize = CGSize(width: 42.0, height: 42.0)
private let rewindButtonSize = CGSize(width: 42.0, height: 42.0)
private let durationSliderHeight: CGFloat = 34.0
private let timeRemainingLabelSize = CGSize(width: 120.0, height: 34.0)
private let timeRemainingLabelSize = CGSize(width: 80, height: 34.0)
private let settingButtonSize = CGSize(width: 24.0, height: 24.0)
private let fullScreenButtonSize = CGSize(width: 20.0, height: 20.0)
private let tableSettingSize = CGSize(width: 110.0, height: 100.0)
private let nextButtonSize = CGSize(width: 42.0, height: 42.0)
private let seekAnimationDuration = 0.4
private let seekLabelSize : CGFloat = OEXTextStyle.pointSize(for: OEXTextSize.base)
private let seekBackwardDuration: Double = 10
private let seekForwardDuration: Double = 15

var video : OEXHelperVideoDownload? {
didSet {
Expand Down Expand Up @@ -81,14 +90,47 @@ class VideoPlayerControls: UIView, VideoPlayerSettingsDelegate {
return view
}()

lazy private var seekForwardLabel : UILabel = {
let label = UILabel()
label.backgroundColor = .clear
label.textColor = .white
label.alpha = 0.0
label.font = environment.styles.sansSerif(ofSize: seekLabelSize)
label.layer.shouldRasterize = true
return label
}()

lazy private var seekRewindLabel : UILabel = {
let label = UILabel()
label.backgroundColor = .clear
label.textColor = .white
label.alpha = 0.0
label.font = environment.styles.sansSerif(ofSize: seekLabelSize)
label.layer.shouldRasterize = true
return label
}()

lazy private var rewindButton: CustomPlayerButton = {
let button = CustomPlayerButton()
button.setImage(UIImage.RewindIcon(), for: .normal)
button.tintColor = .white
button.oex_addAction({[weak self] (action) in
if let weakSelf = self {
weakSelf.delegate?.seekBackwardPressed(playerControls: weakSelf)
}
button.oex_addAction({ [weak self] action in
guard let weakSelf = self, weakSelf.durationSliderValue > weakSelf.durationSlider.minimumValue else { return }
weakSelf.delegate?.seekVideo(playerControls: weakSelf, skipDuration: weakSelf.seekBackwardDuration, type: .rewind)
weakSelf.seekAnimation(seekLabel: weakSelf.seekRewindLabel, seekType: .rewind, animationOffset: 45)
}, for: .touchUpInside)
return button
}()

lazy private var forwardButton: CustomPlayerButton = {
let button = CustomPlayerButton()
button.setImage(UIImage.RewindIcon(), for: .normal)
button.imageView?.transform = CGAffineTransform(scaleX: -1, y: 1); //Flipped
button.tintColor = .white
button.oex_addAction({ [weak self] action in
guard let weakSelf = self, weakSelf.durationSliderValue < weakSelf.durationSlider.maximumValue - 0.001 else { return }
weakSelf.delegate?.seekVideo(playerControls: weakSelf, skipDuration: weakSelf.seekForwardDuration, type: .forward)
weakSelf.seekAnimation(seekLabel: weakSelf.seekForwardLabel, seekType: .forward, animationOffset: 50)
}, for: .touchUpInside)
return button
}()
Expand All @@ -99,7 +141,7 @@ class VideoPlayerControls: UIView, VideoPlayerSettingsDelegate {
slider.setThumbImage(UIImage(named: "ic_seek_thumb.png"), for: .normal)
slider.setMinimumTrackImage(UIImage(named: "ic_progressbar.png"), for: .normal)
slider.secondaryTrackColor = UIColor(red: 76.0/255.0, green: 135.0/255.0, blue: 130.0/255.0, alpha: 0.9)
slider.oex_addAction({[weak self] (action) in
slider.oex_addAction({ [weak self] (action) in
if let weakSelf = self {
weakSelf.delegate?.sliderValueChanged(playerControls: weakSelf)
}
Expand Down Expand Up @@ -243,7 +285,6 @@ class VideoPlayerControls: UIView, VideoPlayerSettingsDelegate {
addSubview(topBar)
topBar.addSubview(videoTitleLabel)
addSubview(bottomBar)
bottomBar.addSubview(rewindButton)
bottomBar.addSubview(durationSlider)
bottomBar.addSubview(timeRemainingLabel)
bottomBar.addSubview(btnSettings)
Expand All @@ -252,9 +293,13 @@ class VideoPlayerControls: UIView, VideoPlayerSettingsDelegate {
addSubview(btnNext)
addSubview(btnPrevious)
addSubview(playPauseButton)
addSubview(rewindButton)
addSubview(forwardButton)
addSubview(subTitleLabel)
addSubview(tableSettings)

addSubview(seekForwardLabel)
addSubview(seekRewindLabel)

sendSubviewToBack(tapButton)
}

Expand Down Expand Up @@ -317,14 +362,35 @@ class VideoPlayerControls: UIView, VideoPlayerSettingsDelegate {
}

rewindButton.snp.makeConstraints { make in
make.leading.equalTo(self).offset(StandardVerticalMargin)
make.trailing.equalTo(playPauseButton).inset(110)
make.height.equalTo(rewindButtonSize.height)
make.width.equalTo(rewindButtonSize.width)
make.centerY.equalTo(self.snp.centerY)
}

forwardButton.snp.makeConstraints { make in
make.leading.equalTo(playPauseButton).inset(112)
make.height.equalTo(rewindButtonSize.height)
make.width.equalTo(rewindButtonSize.width)
make.centerY.equalTo(self.snp.centerY)
}

seekForwardLabel.snp.makeConstraints { make in
make.centerX.equalTo(forwardButton.snp.centerX).offset(5)
make.centerY.equalTo(forwardButton.snp.centerY)
make.height.equalTo(rewindButtonSize.height)
make.width.equalTo(rewindButtonSize.width)
}

seekRewindLabel.snp.makeConstraints { make in
make.centerX.equalTo(rewindButton.snp.centerX).offset(5)
make.centerY.equalTo(rewindButton.snp.centerY)
make.height.equalTo(rewindButtonSize.height)
make.width.equalTo(rewindButtonSize.width)
make.centerY.equalTo(bottomBar.snp.centerY)
}

durationSlider.snp.makeConstraints { make in
make.leading.equalTo(rewindButton.snp.trailing).offset(StandardVerticalMargin)
make.leading.equalTo(self).offset(StandardVerticalMargin)
make.height.equalTo(durationSliderHeight)
make.centerY.equalTo(bottomBar.snp.centerY)
}
Expand Down Expand Up @@ -396,6 +462,8 @@ class VideoPlayerControls: UIView, VideoPlayerSettingsDelegate {
btnNext.accessibilityLabel = Strings.next
rewindButton.accessibilityLabel = Strings.accessibilityRewind
rewindButton.accessibilityHint = Strings.accessibilityRewindHint
forwardButton.accessibilityLabel = Strings.accessibilityForward
forwardButton.accessibilityHint = Strings.accessibilityForwardHint
btnSettings.accessibilityLabel = Strings.accessibilitySettings
fullScreenButton.accessibilityLabel = Strings.accessibilityFullscreen
playPauseButton.setAccessibilityLabelsForStateNormal(normalStateLabel: Strings.accessibilityPause, selectedStateLabel: Strings.accessibilityPlay)
Expand Down Expand Up @@ -429,11 +497,17 @@ class VideoPlayerControls: UIView, VideoPlayerSettingsDelegate {
self?.bottomBar.isUserInteractionEnabled = !isHidden
self?.playPauseButton.alpha = alpha
self?.playPauseButton.isUserInteractionEnabled = !isHidden
self?.rewindButton.alpha = alpha
self?.rewindButton.isUserInteractionEnabled = !isHidden
self?.forwardButton.alpha = alpha
self?.forwardButton.isUserInteractionEnabled = !isHidden
self?.btnPrevious.alpha = alpha
self?.btnNext.alpha = alpha
self?.btnNext.isUserInteractionEnabled = !isHidden
self?.btnPrevious.alpha = alpha
self?.btnPrevious.isUserInteractionEnabled = !isHidden
self?.seekRewindLabel.alpha = 0
self?.seekForwardLabel.alpha = 0

if (!isHidden) {
if let owner = self {
Expand Down Expand Up @@ -487,19 +561,19 @@ class VideoPlayerControls: UIView, VideoPlayerSettingsDelegate {
autoHide()
}
}

@objc private func hideControls() {
hideAndShowControls(isHidden: true)
}

@objc private func showControls() {
hideAndShowControls(isHidden: false)
}

private func settingsButtonClicked() {
NSObject.cancelPreviousPerformRequests(withTarget:self)
tableSettings.isHidden = !tableSettings.isHidden

if tableSettings.isHidden {
autoHide()
}
Expand Down Expand Up @@ -586,6 +660,24 @@ class VideoPlayerControls: UIView, VideoPlayerSettingsDelegate {
NSObject.cancelPreviousPerformRequests(withTarget: self)
stopBufferedTimer()
}

func seekAnimation(seekLabel: UILabel, seekType: SeekType, animationOffset: CGFloat) {
autoHide()
if !isAnimating {
isAnimating = true
let defaultFrame = seekLabel.frame
seekLabel.text = seekType == .rewind ? String(format: "-%d", Int(seekBackwardDuration)) : String(format: "+%d", Int(seekForwardDuration))
UIView.animate(withDuration: seekAnimationDuration, delay: 0.0, options: UIView.AnimationOptions.curveEaseOut, animations: {
seekLabel.alpha = 1.0
let offset = seekType == .forward ? animationOffset : -animationOffset
seekLabel.frame = CGRect(x: defaultFrame.origin.x + offset, y: defaultFrame.origin.y, width: defaultFrame.size.width, height: defaultFrame.size.height)
}) { [weak self] finished in
seekLabel.frame = defaultFrame
seekLabel.alpha = 0.0
self?.isAnimating = false
}
}
}
}

// Specific for test cases
Expand Down
2 changes: 1 addition & 1 deletion Source/OEXAnalytics.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ NS_ASSUME_NONNULL_BEGIN
CourseID:(NSString*)courseId
UnitURL:(NSString*)unitUrl;

- (void)trackVideoSeekRewind:(NSString*)videoId
- (void)trackVideoSeek:(NSString*)videoId
RequestedDuration:(NSTimeInterval)requestedDuration
OldTime:(NSTimeInterval)oldTime
NewTime:(NSTimeInterval)newTime
Expand Down
2 changes: 1 addition & 1 deletion Source/OEXAnalytics.m
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ - (void)trackTranscriptLanguage:(NSString*)videoID CurrentTime:(NSTimeInterval)c
[self trackVideoPlayerEvent:event withInfo:info];
}

- (void)trackVideoSeekRewind:(NSString*)videoId
- (void)trackVideoSeek:(NSString*)videoId
RequestedDuration:(NSTimeInterval)requestedDuration
OldTime:(NSTimeInterval)oldTime
NewTime:(NSTimeInterval)newTime
Expand Down
4 changes: 2 additions & 2 deletions Source/OEXConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ typedef NS_ENUM (NSUInteger, OEXVideoState) {

/**Video player speeds*/
typedef NS_ENUM(NSUInteger, OEXVideoSpeed) {
OEXVideoSpeedDefault, //1.0x
OEXVideoSpeedXXSlow, // .25x
OEXVideoSpeedXSlow, // .5x
OEXVideoSpeedSlow, // .75x
OEXVideoSpeedDefault, //1.0x
OEXVideoSpeedFast, // 1.25x
OEXVideoSpeedXFast, // 1.5x
OEXVideoSpeedXXFast, // 1.75x
OEXVideoSpeedXXXFast, // 2x
OEXVideoSpeedXXXFast // 2x
};

//Wifi only Key
Expand Down
4 changes: 4 additions & 0 deletions Source/OEXInterface.m
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,10 @@ + (void)setCCSelectedPlaybackSpeed:(OEXVideoSpeed) speed {
}

+ (OEXVideoSpeed)getCCSelectedPlaybackSpeed {

if(![[NSUserDefaults standardUserDefaults] objectForKey:PERSIST_PLAYBACKSPEED]) {
return OEXVideoSpeedDefault;
}
return [[NSUserDefaults standardUserDefaults] integerForKey:PERSIST_PLAYBACKSPEED];
}

Expand Down
6 changes: 5 additions & 1 deletion Source/ar.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@
/* Accessibility label for Rewind Button in the Video Player */
"ACCESSIBILITY_REWIND" = "رجوع";
/* Call to action for Rewind Button in the Video Player */
"ACCESSIBILITY_REWIND_HINT" = "رجوع بفاصل زمني 30 ثانية";
"ACCESSIBILITY_REWIND_HINT" = "رجوع بفاصل زمني 10 ثانية";
/* Accessibility label for Forward Button in the Video Player */
"ACCESSIBILITY_FORWARD" = "Forward";
/* Call to action for forward Button in the Video Player */
"ACCESSIBILITY_FORWARD_HINT" = "Forward by 15 seconds.";
/* Accessibility label for Seek bar in the Video Player */
"ACCESSIBILITY_SEEK_BAR" = "شريط التتبّع";
/* Accessibility label for Settings Button in the Video Player */
Expand Down
6 changes: 5 additions & 1 deletion Source/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@
/* Accessibility label for Rewind Button in the Video Player */
"ACCESSIBILITY_REWIND" = "Zurückspulen";
/* Call to action for Rewind Button in the Video Player */
"ACCESSIBILITY_REWIND_HINT" = "Um 30 Sekunden zurückspulen.";
"ACCESSIBILITY_REWIND_HINT" = "Um 10 Sekunden zurückspulen.";
/* Accessibility label for Forward Button in the Video Player */
"ACCESSIBILITY_FORWARD" = "Forward";
/* Call to action for forward Button in the Video Player */
"ACCESSIBILITY_FORWARD_HINT" = "Forward by 15 seconds.";
/* Accessibility label for Seek bar in the Video Player */
"ACCESSIBILITY_SEEK_BAR" = "Suche";
/* Accessibility label for Settings Button in the Video Player */
Expand Down
Loading

0 comments on commit db2bcf8

Please sign in to comment.