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

Commit

Permalink
chore: update chromecast for app nav (#1811)
Browse files Browse the repository at this point in the history
* chore: add chromecast to app-nav

* chore: address feedback
  • Loading branch information
Muhammad Umer authored Nov 21, 2023
1 parent 6ca126c commit eeb90aa
Show file tree
Hide file tree
Showing 14 changed files with 132 additions and 185 deletions.
65 changes: 16 additions & 49 deletions Source/ChromeCastButtonDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,70 +9,37 @@
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 ChromecastView: UIView, ChromeCastButtonDelegate {
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
}
addSubview(castButton)
castButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.centerY.equalToSuperview()
make.width.height.equalTo(buttonSize)
}

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)
}

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 = ChromecastView()

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.add(delegate: self)
}

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

0 comments on commit eeb90aa

Please sign in to comment.