Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Video in background #261

Merged
merged 6 commits into from
Apr 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Stepic/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
Expand Down Expand Up @@ -89,6 +87,10 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand All @@ -97,6 +99,8 @@
<array>
<string>armv7</string>
</array>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
Expand Down
54 changes: 36 additions & 18 deletions Stepic/Player.swift
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,15 @@ public class Player: UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()

if RemoteConfig.shared.allowVideoInBackground {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("failed to set up background playing: \(error)")
}
}

self.playerView.layer.addObserver(self, forKeyPath: PlayerReadyForDisplayKey, options: ([.new, .old]), context: &PlayerLayerObserverContext)
self.timeObserver = self.avplayer.addPeriodicTimeObserver(forInterval: CMTimeMake(1, 100), queue: DispatchQueue.main, using: { [weak self] _ in
guard let strongSelf = self else { return }
Expand Down Expand Up @@ -468,28 +477,41 @@ extension Player {
// UIApplication

internal func addApplicationObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(_:)), name: NSNotification.Name.UIApplicationWillResignActive, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground(_:)), name: NSNotification.Name.UIApplicationDidEnterBackground, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground(_:)), name: NSNotification.Name.UIApplicationWillEnterForeground, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationWillResignActive(_:)), name: .UIApplicationWillResignActive, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationDidBecomeActive(_:)), name: .UIApplicationDidBecomeActive, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationDidEnterBackground(_:)), name: .UIApplicationDidEnterBackground, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationWillEnterForeground(_:)), name: .UIApplicationWillEnterForeground, object: UIApplication.shared)
}

internal func removeApplicationObservers() {
}

@objc internal func applicationWillResignActive(_ aNotification: NSNotification) {
if self.playbackState == .playing {
@objc internal func handleApplicationWillResignActive(_ aNotification: Notification) {
if !RemoteConfig.shared.allowVideoInBackground && self.playbackState == .playing {
self.pause()
}
}

@objc internal func applicationDidEnterBackground(_ aNotification: NSNotification) {
if self.playbackState == .playing {
self.pause()
@objc internal func handleApplicationDidBecomeActive(_ aNotification: Notification) {
if RemoteConfig.shared.allowVideoInBackground {
// Attach AVPlayer to AVPlayerLayer again
playerView.player = self.avplayer
}
}

@objc internal func handleApplicationDidEnterBackground(_ aNotification: Notification) {
if RemoteConfig.shared.allowVideoInBackground {
// Detach AVPlayer from AVPlayerLayer (from Apple's manual)
playerView.player = nil
} else {
if self.playbackState == .playing {
self.pause()
}
}
}

@objc internal func applicationWillEnterForeground(_ aNoticiation: NSNotification) {
if self.playbackState == .paused {
@objc internal func handleApplicationWillEnterForeground(_ aNoticiation: Notification) {
if !RemoteConfig.shared.allowVideoInBackground && self.playbackState == .paused {
self.playFromCurrentTime()
}
}
Expand Down Expand Up @@ -622,21 +644,17 @@ extension Player {

internal class PlayerView: UIView {

var player: AVPlayer! {
var player: AVPlayer? {
get {
return (self.layer as! AVPlayerLayer).player
return playerLayer.player
}
set {
if (self.layer as! AVPlayerLayer).player != newValue {
(self.layer as! AVPlayerLayer).player = newValue
}
playerLayer.player = newValue
}
}

var playerLayer: AVPlayerLayer {
get {
return self.layer as! AVPlayerLayer
}
return layer as! AVPlayerLayer
}

var fillMode: String {
Expand Down
13 changes: 12 additions & 1 deletion Stepic/RemoteConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ enum RemoteConfigKeys: String {
case showStreaksNotificationTrigger = "show_streaks_notification_trigger"
case adaptiveBackendUrl = "adaptive_backend_url"
case supportedInAdaptiveModeCourses = "adaptive_courses_ios"
case allowVideoInBackground = "allow_video_in_background"
}

class RemoteConfig {
private let defaultShowStreaksNotificationTrigger = ShowStreaksNotificationTrigger.loginAndSubmission
private let defaultAllowVideoInBackground = false
static let shared = RemoteConfig()

var loadingDoneCallback: (() -> Void)?
Expand All @@ -25,7 +27,8 @@ class RemoteConfig {
lazy var appDefaults: [String: NSObject] = [
RemoteConfigKeys.showStreaksNotificationTrigger.rawValue: defaultShowStreaksNotificationTrigger.rawValue as NSObject,
RemoteConfigKeys.adaptiveBackendUrl.rawValue: StepicApplicationsInfo.adaptiveRatingURL as NSObject,
RemoteConfigKeys.supportedInAdaptiveModeCourses.rawValue: StepicApplicationsInfo.adaptiveSupportedCourses as NSObject
RemoteConfigKeys.supportedInAdaptiveModeCourses.rawValue: StepicApplicationsInfo.adaptiveSupportedCourses as NSObject,
RemoteConfigKeys.allowVideoInBackground.rawValue: defaultAllowVideoInBackground as NSObject
]

enum ShowStreaksNotificationTrigger: String {
Expand Down Expand Up @@ -57,6 +60,14 @@ class RemoteConfig {
return ids
}

var allowVideoInBackground: Bool {
guard let configValue = FirebaseRemoteConfig.RemoteConfig.remoteConfig().configValue(forKey: RemoteConfigKeys.allowVideoInBackground.rawValue).stringValue else {
return defaultAllowVideoInBackground
}

return configValue == "true"
}

init() {
loadDefaultValues()
fetchCloudValues()
Expand Down
37 changes: 36 additions & 1 deletion Stepic/StepicVideoPlayerViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ class StepicVideoPlayerViewController: UIViewController {
fileprivate var player: Player!

var video: Video!
var videoInBackgroundTooltip: Tooltip?

override func viewDidLoad() {
super.viewDidLoad()
Expand All @@ -238,6 +239,8 @@ class StepicVideoPlayerViewController: UIViewController {
WatchSessionSender.sendPlaybackStatus(.available)

NotificationCenter.default.addObserver(self, selector: #selector(StepicVideoPlayerViewController.audioRouteChanged(_:)), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationDidBecomeActive(_:)), name: .UIApplicationDidBecomeActive, object: UIApplication.shared)
NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationDidEnterBackground(_:)), name: .UIApplicationDidEnterBackground, object: UIApplication.shared)

topTimeSlider.setThumbImage(Images.playerControls.timeSliderThumb, for: UIControlState())

Expand Down Expand Up @@ -275,7 +278,34 @@ class StepicVideoPlayerViewController: UIViewController {
topTimeSlider.addTarget(self, action: #selector(StepicVideoPlayerViewController.finishedSeeking), for: UIControlEvents.touchUpInside)
topTimeSlider.addTarget(self, action: #selector(StepicVideoPlayerViewController.startedSeeking), for: UIControlEvents.touchDown)
MPRemoteCommandCenter.shared().togglePlayPauseCommand.addTarget(self, action: #selector(StepicVideoPlayerViewController.togglePlayPause))
// MPRemoteCommandCenter.sharedCommandCenter().togglePlayPauseCommand.addTarget(self, action: #selector(togglePlayStop(_:)));
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

if RemoteConfig.shared.allowVideoInBackground && TooltipDefaultsManager.shared.shouldShowInVideoPlayer {
delay(2.0) { [weak self] in
guard let s = self else {
return
}

s.videoInBackgroundTooltip = TooltipFactory.videoInBackground
s.videoInBackgroundTooltip?.show(direction: .down, in: s.view, from: s.fullscreenPlayButton, isArrowVisible: false)
TooltipDefaultsManager.shared.didShowInVideoPlayer = true
}
}
}

@objc internal func handleApplicationDidEnterBackground(_ aNotification: Notification) {
if !RemoteConfig.shared.allowVideoInBackground {
MPRemoteCommandCenter.shared().togglePlayPauseCommand.removeTarget(self)
}
}

@objc internal func handleApplicationDidBecomeActive(_ aNotification: Notification) {
if !RemoteConfig.shared.allowVideoInBackground {
MPRemoteCommandCenter.shared().togglePlayPauseCommand.addTarget(self, action: #selector(StepicVideoPlayerViewController.togglePlayPause))
}
}

@objc func togglePlayPause() {
Expand Down Expand Up @@ -389,9 +419,14 @@ class StepicVideoPlayerViewController: UIViewController {

extension StepicVideoPlayerViewController : PlayerDelegate {
func playerReady(_ player: Player) {
guard player.playbackState == .stopped else {
return
}

print("player is ready to display")
activityIndicator.isHidden = true
setTimeParametersAfterPlayerIsReady()

player.seekToTime(CMTime(seconds: playerStartTime, preferredTimescale: 1000))
player.playFromCurrentTime()
player.rate = currentRate.rawValue
Expand Down
21 changes: 18 additions & 3 deletions Stepic/Tooltip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import EasyTipView
protocol Tooltip {
init(text: String, shouldDismissAfterTime: Bool, color: TooltipColor)
func show(direction: TooltipDirection, in inView: UIView?, from fromView: UIView)
func show(direction: TooltipDirection, in inView: UIView?, from fromView: UIView, isArrowVisible: Bool)
func show(direction: TooltipDirection, in inView: UIView?, from fromItem: UIBarButtonItem)
func dismiss()
}
Expand Down Expand Up @@ -85,8 +86,18 @@ class EasyTipTooltip: Tooltip {
preferences.drawing.borderColor = color.borderColor
}

private func setupTooltip(direction: TooltipDirection) {
private func setupTooltip(direction: TooltipDirection, isArrowVisible: Bool) {
preferences.drawing.arrowPosition = easyTipDirectionFromTooltipDirection(direction: direction)

if !isArrowVisible {
switch direction {
case .up, .down:
preferences.drawing.arrowWidth = CGFloat(0)
case .left, .right:
preferences.drawing.arrowHeight = CGFloat(0)
}
}

easyTip = EasyTipView(text: text, preferences: preferences, delegate: nil)
}

Expand All @@ -101,13 +112,17 @@ class EasyTipTooltip: Tooltip {
}

func show(direction: TooltipDirection, in inView: UIView?, from fromView: UIView) {
setupTooltip(direction: direction)
show(direction: direction, in: inView, from: fromView, isArrowVisible: true)
}

func show(direction: TooltipDirection, in inView: UIView?, from fromView: UIView, isArrowVisible: Bool) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

можно просто сделать isArrowVisible: Bool = true дефолтным параметром в методе. Тогда тот, что выше, будет не нужен

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Он под протоколом, а протоколы не разрешают дефолтное значение для аргументов. А если мы уберём метод, который без isArrowVisible и оставим в протоколе 2 метода (для UIView с isArrowVisible и для UIBarButtonItem), то не сможем вызывать метод для UIView.

setupTooltip(direction: direction, isArrowVisible: isArrowVisible)
easyTip.show(forView: fromView, withinSuperview: inView)
setupDisappear()
}

func show(direction: TooltipDirection, in inView: UIView?, from fromItem: UIBarButtonItem) {
setupTooltip(direction: direction)
setupTooltip(direction: direction, isArrowVisible: true)
easyTip.show(forItem: fromItem, withinSuperView: inView)
setupDisappear()
}
Expand Down
15 changes: 15 additions & 0 deletions Stepic/TooltipDefaultsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class TooltipDefaultsManager {
private let didShowOnLessonDownloadsKey = "didShowOnLessonDownloadsKey"
private let didShowOnHomeContinueLearningKey = "didShowOnHomeContinueLearningKey"
private let didShowOnStreaksSwitchInProfileKey = "didShowOnStreaksSwitchInProfileKey"
private let didShowInVideoPlayerKey = "didShowInVideoPlayerKey"

var didShowOnLessonDownloads: Bool {
set(value) {
Expand Down Expand Up @@ -48,6 +49,16 @@ class TooltipDefaultsManager {
}
}

var didShowInVideoPlayer: Bool {
set(value) {
defaults.set(value, forKey: didShowInVideoPlayerKey)
}

get {
return defaults.value(forKey: didShowInVideoPlayerKey) as? Bool ?? false
}
}

var shouldShowOnHomeContinueLearning: Bool {
return !didShowOnHomeContinueLearning
}
Expand All @@ -59,4 +70,8 @@ class TooltipDefaultsManager {
var shouldShowOnStreaksSwitchInProfile: Bool {
return !didShowOnStreaksSwitchInProfile
}

var shouldShowInVideoPlayer: Bool {
return !didShowInVideoPlayer
}
}
4 changes: 4 additions & 0 deletions Stepic/TooltipFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ struct TooltipFactory {
static var streaksTooltip: Tooltip {
return EasyTipTooltip(text: NSLocalizedString("StreaksSwitchTooltip", comment: ""), shouldDismissAfterTime: true, color: .standard)
}

static var videoInBackground: Tooltip {
return EasyTipTooltip(text: NSLocalizedString("VideoInBackgroundTooltip", comment: ""), shouldDismissAfterTime: true, color: .standard)
}
}
1 change: 1 addition & 0 deletions Stepic/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ ShareCourseTooltip = "Share the link with your friends to learn together";
LessonDownloadTooltip = "Download lesson to watch lectures offline";
ContinueLearningWidgetTooltip = "Tap to continue from where you finished last time";
StreaksSwitchTooltip = "Turn on to get new portion of knowledge every day";
VideoInBackgroundTooltip = "You can play video in background";

/* Notification alerts */
NotificationTabNotificationRequestAlertTitle = "Stay tuned";
Expand Down
1 change: 1 addition & 0 deletions Stepic/ru.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ ShareCourseTooltip = "Поделитесь ссылкой с друзьями,
LessonDownloadTooltip = "Загрузите урок, чтобы смотреть видео оффлайн";
ContinueLearningWidgetTooltip = "Нажмите, чтобы перейти к тому месту, где закончили в прошлый раз";
StreaksSwitchTooltip = "Включите, чтобы получать новую порцию знаний каждый день";
VideoInBackgroundTooltip = "Вы можете продолжить воспроизведение видео в фоновом режиме";

/* Notification alerts */
NotificationTabNotificationRequestAlertTitle = "Следи за обновлениями";
Expand Down