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

chore: update chromecast for app nav #1811

Merged
merged 2 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
67 changes: 18 additions & 49 deletions Source/ChromeCastButtonDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,70 +9,39 @@
import Foundation
import GoogleCast

/// Chrome cast button will always be added to the index one in case of more than one right navbar items
let ChromeCastButtonIndex = 1

/// Handles chrome cast button addition and removal from the navigation bar
/// This protocol will handle button addition/removal to navigation bar without consedering the video cast state
protocol ChromeCastButtonDelegate {
var chromeCastButton: GCKUICastButton { get }
var chromeCastButtonItem: UIBarButtonItem { get }
func addChromeCastButton()
func addChromeCastButton(tintColor: UIColor?)
func removeChromecastButton()
}

/// Handles chrome cast button addition and removal from the navigation bar
/// This protocol will handle button addition/removal to navigation bar when the video is being casted: connected state
protocol ChromeCastConnectedButtonDelegate: ChromeCastButtonDelegate {
}

/// Default implementation of Protocol ChromeCastButtonDelegate,
/// This way Controller just needs to add 'ChromeCastButtonDelegate' and it will have all desired functionality
extension ChromeCastButtonDelegate where Self: UIViewController {
/// Provides Reference to ChromeCastButton that will be added to Navigationbar
class ChromecastEnableView: UIView, ChromeCastButtonDelegate {
mumer92 marked this conversation as resolved.
Show resolved Hide resolved
private var buttonSize: CGFloat = 24

var chromeCastButton: GCKUICastButton {
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: 24, height: 24))
castButton.tintColor = OEXStyles.shared().primaryBaseColor()
let castButton = GCKUICastButton(frame: CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize))
castButton.oex_addAction({ _ in
ChromeCastManager.shared.viewExpanded = true
}, for: .touchUpInside)
return castButton
}

var chromeCastButtonItem: UIBarButtonItem {
return UIBarButtonItem(customView: chromeCastButton)
}

func addChromeCastButton() {
guard let count = navigationItem.rightBarButtonItems?.count, count >= 1 else {
navigationItem.rightBarButtonItem = chromeCastButtonItem
return
func addChromeCastButton(tintColor: UIColor? = nil) {
let castButton = chromeCastButton
if let tintColor = tintColor {
castButton.tintColor = tintColor
}

var isAdded = false
navigationItem.rightBarButtonItems?.forEach({ item in
if item.customView is GCKUICastButton {
isAdded = true
return
}
})

if isAdded { return }

navigationItem.rightBarButtonItems?.insert(chromeCastButtonItem, at: ChromeCastButtonIndex)
addSubview(castButton)
castButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
castButton.centerXAnchor.constraint(equalTo: centerXAnchor),
castButton.centerYAnchor.constraint(equalTo: centerYAnchor),
castButton.widthAnchor.constraint(equalToConstant: buttonSize),
castButton.heightAnchor.constraint(equalToConstant: buttonSize)
])
mumer92 marked this conversation as resolved.
Show resolved Hide resolved
}

func removeChromecastButton() {
guard let navigationBarItems = navigationItem.rightBarButtonItems else {
navigationItem.rightBarButtonItem = nil
return
}

for (index, element) in navigationBarItems.enumerated() {
if element.customView is GCKUICastButton {
navigationItem.rightBarButtonItems?.remove(at: index)
break
}
}
subviews.first(where: { $0 is GCKUICastButton })?.removeFromSuperview()
}
}
103 changes: 18 additions & 85 deletions Source/ChromeCastManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ private enum DelegateCallbackType: Int {
return sessionManager?.hasConnectedCastSession() ?? false
}

var isAvailable: Bool {
return discoveryManager?.deviceCount ?? 0 > 0
}

var currentPlayingVideoCourseID: String? {
return sessionManager?.currentCastSession?.remoteMediaClient?.mediaStatus?.mediaInformation?.metadata?.string(forKey: ChromeCastCourseID)
}

private var callbackType: DelegateCallbackType = .none {
didSet {
if oldValue != callbackType {
Expand Down Expand Up @@ -84,9 +92,8 @@ private enum DelegateCallbackType: Int {
}

func add(delegate: ChromeCastPlayerStatusDelegate) {
let conteins = delegates.filter { $0 === delegate }
if conteins.count > 0 { return }

let contains = delegates.filter { $0 === delegate }
if contains.count > 0 { return }
delegates.append(delegate)
}

Expand All @@ -107,21 +114,6 @@ private enum DelegateCallbackType: Int {
currentSession.remoteMediaClient?.remove(self)
}

func showIntroductoryOverlay(items: [UIBarButtonItem]) {
guard let button = items.first else { return }
guard let view = button.customView as? GCKUICastButton, !view.isHidden else { return }
context.presentCastInstructionsViewControllerOnce(with: view)
}

//MARK:- GCKSessionManager methods
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKSession) {
DispatchQueue.main.async { [weak self] in
self?.addMediaListner()
self?.addChromeCastButton()
self?.callbackType = .connect
}
}

private func delegateCallBacks() {
for delegate in delegates {
switch callbackType {
Expand Down Expand Up @@ -170,11 +162,18 @@ private enum DelegateCallbackType: Int {
environment?.interface?.sendAnalyticsEvents(state, withCurrentTime: streamPosition, forVideo: video, playMedium: AnalyticsEventDataKey.PlayMediumChromecast.rawValue)
}

//MARK:- GCKSessionManager methods
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKSession) {
DispatchQueue.main.async { [weak self] in
self?.addMediaListner()
self?.callbackType = .connect
}
}

func sessionManager(_ sessionManager: GCKSessionManager, didEnd session: GCKSession, withError error: Error?) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
self?.removeMediaListener()
self?.callbackType = .disconnect
self?.removeChromeCastButton()
}
}

Expand Down Expand Up @@ -206,70 +205,4 @@ private enum DelegateCallbackType: Int {
break
}
}

//MARK:- ChromeCastButtonDelegate methods
private func addChromeCastButton() {
let topViewcontroller = UIApplication.shared.topMostController()
guard let navController = topViewcontroller?.navigationController as? ForwardingNavigationController else { return }

navController.viewControllers.forEach { [weak self] controller in
self?.addChromeCastButton(over: controller)
}
}

func addChromeCastButton(over controller: UIViewController) {
guard let controller = controller as? ChromeCastButtonDelegate else { return }

if controller is ChromeCastConnectedButtonDelegate {
if isConnected {
controller.addChromeCastButton()
}
}
else {
controller.addChromeCastButton()
}
}

private func removeChromeCastButton() {
let topViewcontroller = UIApplication.shared.topMostController()
guard let navController = topViewcontroller?.navigationController as? ForwardingNavigationController else { return }

navController.viewControllers.forEach { [weak self] controller in
self?.removeChromeCastButton(from: controller)
}
}

func removeChromeCastButton(from controller: UIViewController, force: Bool = false) {
guard let controller = controller as? ChromeCastButtonDelegate else { return }

if force {
controller.removeChromecastButton()
return
}

if controller is ChromeCastConnectedButtonDelegate {
controller.removeChromecastButton()
}
}

func handleCastButton(for controller: UIViewController) {
guard let _ = controller as? ChromeCastButtonDelegate else { return }
// Delay of 4 seconds is added as it takes framework to
// initialize and return true if it has already established connection
let delay: Double = isInitilized ? 0 : 4

if !isInitilized {
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
if !(self?.isInitilized ?? true) && self?.isConnected ?? false {
// Add media listner if video is being casted, ideally chromecast SDK shuold configure it automatically but unfortunately, its not configuring media listener. So adding media listner manually
self?.addMediaListner()
}
self?.isInitilized = true
self?.addChromeCastButton(over: controller)
}
}
else {
addChromeCastButton(over: controller)
}
}
}
4 changes: 3 additions & 1 deletion Source/ChromeCastMetaDataModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import Foundation
import GoogleCast

let ChromeCastVideoID = "ChromeCastVideoID"
let ChromeCastCourseID = "ChromeCastCourseID"

/// Extension of GCKMediaInformation to allow building of media information with meta data to be provided to chrome cast Device
extension GCKMediaInformation {
static func buildMediaInformation(contentID: String, title: String, videoID: String, contentType: ChromeCastContentType, streamType: GCKMediaStreamType, thumbnailUrl: String?, deviceName: String?) -> GCKMediaInformation {
static func buildMediaInformation(courseID: String, contentID: String, title: String, videoID: String, contentType: ChromeCastContentType, streamType: GCKMediaStreamType, thumbnailUrl: String?, deviceName: String?) -> GCKMediaInformation {
let metadata = GCKMediaMetadata(metadataType: .movie)
metadata.setString(title, forKey: kGCKMetadataKeyTitle)
metadata.setString(deviceName ?? "", forKey: kGCKMetadataKeyStudio)
metadata.setString(courseID, forKey: ChromeCastCourseID)
metadata.setString(videoID, forKey: ChromeCastVideoID)

if let thumbnailUrl = thumbnailUrl, let url = URL(string: thumbnailUrl) {
Expand Down
8 changes: 0 additions & 8 deletions Source/ContainerNavigationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,3 @@ extension UINavigationController {
coordinator.animate(alongsideTransition: nil) { _ in completion?() }
}
}

extension ForwardingNavigationController {
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
super.pushViewController(viewController, animated: animated)

ChromeCastManager.shared.handleCastButton(for: viewController)
}
}
3 changes: 1 addition & 2 deletions Source/CourseContentPageViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extension CourseBlockDisplayType {
}

// Container for scrolling horizontally between different screens of course content
public class CourseContentPageViewController : UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, CourseBlockViewController, InterfaceOrientationOverriding, ChromeCastButtonDelegate {
public class CourseContentPageViewController : UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, CourseBlockViewController, InterfaceOrientationOverriding {

public typealias Environment = OEXAnalyticsProvider & DataManagerProvider & OEXRouterProvider & OEXConfigProvider

Expand Down Expand Up @@ -122,7 +122,6 @@ public class CourseContentPageViewController : UIPageViewController, UIPageViewC
}
addObservers()
addRestrictedViewToPagination()
ChromeCastManager.shared.removeChromeCastButton(from: self, force: true)
}

// This is to restrict the pagination for bottom bar of player to make player progress slider smooth
Expand Down
59 changes: 58 additions & 1 deletion Source/CourseDashboardHeaderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class CourseDashboardHeaderView: UIView {
return button
}()

private lazy var chromercastView = ChromecastEnableView()

private lazy var certificateView: CourseCertificateView? = nil

private func addCertificateView() {
Expand Down Expand Up @@ -185,6 +187,12 @@ class CourseDashboardHeaderView: UIView {
// it will be used to hide value prop from header in favor of embeded value prop on course dashboard
private var hideValueProp: Bool = false

private var chromeCastManager = ChromeCastManager.shared

private var isChromeCastConnected: Bool {
return chromeCastManager.isConnected && chromeCastManager.currentPlayingVideoCourseID == course?.course_id
}

init(environment: Environment, course: OEXCourse?, tabbarItems: [TabBarItem], error: CourseAccessHelper?) {
self.environment = environment
self.course = course
Expand All @@ -195,6 +203,8 @@ class CourseDashboardHeaderView: UIView {
addSubViews()
setOrUpdateConstraints()
configureView()

ChromeCastManager.shared.add(delegate: self)
mumer92 marked this conversation as resolved.
Show resolved Hide resolved
}

private func configureView() {
Expand Down Expand Up @@ -234,6 +244,23 @@ class CourseDashboardHeaderView: UIView {
make.edges.equalTo(self)
}

if isChromeCastConnected {
if !containerView.subviews.contains(chromercastView) {
containerView.addSubview(chromercastView)
}

chromercastView.addChromeCastButton(tintColor: environment.styles.neutralWhiteT())

chromercastView.snp.remakeConstraints { make in
make.top.equalTo(containerView).offset(StandardVerticalMargin * 2)
make.trailing.equalTo(closeButton.snp.leading).offset(-StandardHorizontalMargin)
make.height.equalTo(imageSize)
make.width.equalTo(imageSize)
}
} else {
chromercastView.removeFromSuperview()
}

closeButton.snp.remakeConstraints { make in
make.top.equalTo(containerView).offset(StandardVerticalMargin * 2)
make.trailing.equalTo(containerView).inset(StandardVerticalMargin * 2)
Expand All @@ -245,7 +272,7 @@ class CourseDashboardHeaderView: UIView {
make.top.equalTo(closeButton)
make.centerY.equalTo(closeButton)
make.leading.equalTo(containerView).offset(StandardHorizontalMargin)
make.trailing.equalTo(closeButton.snp.leading).offset(-StandardHorizontalMargin)
make.trailing.equalTo(isChromeCastConnected ? chromercastView.snp.leading : closeButton.snp.leading).offset(-StandardHorizontalMargin)
}

if state == .collapsed { return }
Expand Down Expand Up @@ -391,10 +418,40 @@ class CourseDashboardHeaderView: UIView {
hideValueProp = hide
setOrUpdateConstraints()
}

func reset() {
chromeCastManager.remove(delegate: self)
}
}

extension CourseDashboardHeaderView: CourseDashboardTabbarViewDelegate {
func didSelectItem(at position: Int, tabbarItem: TabBarItem) {
delegate?.didTapTabbarItem(at: position, tabbarItem: tabbarItem)
}
}

extension CourseDashboardHeaderView: ChromeCastPlayerStatusDelegate {
func chromeCastDidConnect() {
guard let course = course else { return }
let currentPlayingCourseID = chromeCastManager.currentPlayingVideoCourseID
if currentPlayingCourseID == course.course_id {
setOrUpdateConstraints()
}
}

func chromeCastDidDisconnect(playedTime: TimeInterval) {
setOrUpdateConstraints()
}

func chromeCastVideoPlaying() {
guard let course = course else { return }
let currentPlayingCourseID = chromeCastManager.currentPlayingVideoCourseID
if currentPlayingCourseID == course.course_id {
setOrUpdateConstraints()
}
}

func chromeCastDidFinishPlaying() {

}
}
2 changes: 1 addition & 1 deletion Source/CourseDashboardViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import UIKit

class CourseDashboardViewController: UITabBarController, InterfaceOrientationOverriding, ChromeCastConnectedButtonDelegate {
class CourseDashboardViewController: UITabBarController, InterfaceOrientationOverriding {

typealias Environment = OEXAnalyticsProvider & OEXConfigProvider & DataManagerProvider & NetworkManagerProvider & OEXRouterProvider & OEXInterfaceProvider & ReachabilityProvider & OEXSessionProvider & OEXStylesProvider & RemoteConfigProvider & ServerConfigProvider

Expand Down
Loading
Loading