├── .gitignore ├── .idea ├── assetWizardSettings.xml ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── kotlinc.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── build.gradle ├── chromecast-receiver ├── css │ └── styles.css ├── index.html └── js │ ├── YouTubePlayer.js │ ├── io │ ├── ChromecastCommunicationChannel.js │ ├── SenderMessagesDispatcher.js │ ├── YouTubeMessage.js │ └── YouTubePlayerRemoteBridge.js │ └── main.js ├── chromecast-sender ├── .gitignore ├── build.gradle ├── installAndDeployConfigFiles │ ├── bintray_v1.gradle │ └── install_v1.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── pierfrancescosoffritti │ │ └── androidyoutubeplayer │ │ └── chromecast │ │ └── chromecastsender │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── pierfrancescosoffritti │ │ │ └── androidyoutubeplayer │ │ │ └── chromecast │ │ │ └── chromecastsender │ │ │ ├── ChromecastYouTubePlayer.kt │ │ │ ├── ChromecastYouTubePlayerContext.kt │ │ │ ├── io │ │ │ ├── infrastructure │ │ │ │ ├── CastSessionListener.kt │ │ │ │ ├── CastSessionManagerListener.kt │ │ │ │ ├── ChromecastCommunicationChannel.kt │ │ │ │ ├── ChromecastConnectionListener.kt │ │ │ │ ├── ChromecastManager.kt │ │ │ │ └── MessageFromReceiver.kt │ │ │ └── youtube │ │ │ │ ├── ChromecastCommunicationConstants.kt │ │ │ │ ├── ChromecastYouTubeIOChannel.kt │ │ │ │ └── ChromecastYouTubeMessageDispatcher.kt │ │ │ └── utils │ │ │ └── JSONUtils.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── pierfrancescosoffritti │ └── androidyoutubeplayer │ └── chromecast │ └── chromecastsender │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample-app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── pierfrancescosoffritti │ │ └── cyplayersample │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── pierfrancescosoffritti │ │ │ └── cyplayersample │ │ │ ├── MainActivity.kt │ │ │ ├── examples │ │ │ ├── CastOptionsProvider.kt │ │ │ ├── basicExample │ │ │ │ └── BasicExampleActivity.kt │ │ │ ├── localPlayerExample │ │ │ │ ├── LocalPlayerInitExampleActivity.kt │ │ │ │ ├── MediaRouteButtonContainer.kt │ │ │ │ └── YouTubePlayersManager.kt │ │ │ ├── notificationExample │ │ │ │ └── NotificationExampleActivity.kt │ │ │ └── playerControlsExample │ │ │ │ └── PlayerControlsExample.kt │ │ │ ├── notifications │ │ │ ├── NotificationManager.kt │ │ │ └── PlaybackControllerBroadcastReceiver.kt │ │ │ ├── ui │ │ │ └── SimpleChromecastUIController.kt │ │ │ └── utils │ │ │ ├── MediaRouteButtonUtils.kt │ │ │ ├── NetworkUtils.kt │ │ │ ├── PlayServicesUtils.kt │ │ │ ├── PlaybackUtils.kt │ │ │ ├── VideoInfo.kt │ │ │ └── YouTubeDataEndpoint.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_cast_connected_24dp.xml │ │ ├── ic_github_24dp.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_nav_drawer_menu_24dp.xml │ │ ├── ic_pause_24dp.xml │ │ ├── ic_play_arrow_24dp.xml │ │ └── ic_playstore_24dp.xml │ │ ├── layout │ │ ├── activity_basic_example.xml │ │ ├── activity_main.xml │ │ ├── activity_notification_example.xml │ │ ├── activity_player_controls_example.xml │ │ └── local_and_cast_player_example.xml │ │ ├── menu │ │ ├── drawer_menu.xml │ │ └── main_activity_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── pierfrancescosoffritti │ └── cyplayersample │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/assetWizardSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 106 | 107 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PierfrancescoSoffritti/chromecast-youtube-player/2197bbb16f56fbda27ac5e8c16a8a0e8b1bd7631/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Pierfrancesco Soffritti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChromecastYouTubeSample 2 | 3 | This project has been implemented as an extension for the android-youtube-player library. Read more on the project's [readme](https://github.com/PierfrancescoSoffritti/android-youtube-player#table-of-contents-chromecast). 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.2.50' 3 | ext.dokka_version = '0.9.17' 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.1.3' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | 12 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' 13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 14 | 15 | classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:$dokka_version" 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | maven { url "https://jitpack.io" } 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /chromecast-receiver/css/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | width: 100%; 4 | margin: 0; 5 | padding: 0; 6 | background-color: #000000; 7 | overflow: hidden; 8 | position: fixed; 9 | } -------------------------------------------------------------------------------- /chromecast-receiver/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | 22 | chromecast-youtube-player-receiver 23 | 24 | 25 | 26 | 27 |
28 | 29 | -------------------------------------------------------------------------------- /chromecast-receiver/js/YouTubePlayer.js: -------------------------------------------------------------------------------- 1 | import YouTubePlayerRemoteBridge from "./io/YouTubePlayerRemoteBridge.js" 2 | 3 | function YouTubePlayer(communicationConstants, communicationChannel) { 4 | const UNSTARTED = "UNSTARTED" 5 | const ENDED = "ENDED" 6 | const PLAYING = "PLAYING" 7 | const PAUSED = "PAUSED" 8 | const BUFFERING = "BUFFERING" 9 | const CUED = "CUED" 10 | 11 | const YouTubePlayerBridge = new YouTubePlayerRemoteBridge(communicationConstants, communicationChannel) 12 | 13 | let player 14 | let lastState 15 | let lastVideoId 16 | 17 | function initialize() { 18 | YouTubePlayerBridge.sendYouTubeIframeAPIReady() 19 | 20 | player = new YT.Player('youTubePlayerDOM', { 21 | 22 | height: '100%', 23 | width: '100%', 24 | 25 | events: { 26 | onReady: () => YouTubePlayerBridge.sendReady(), 27 | onStateChange: event => sendPlayerStateChange(event.data), 28 | onPlaybackQualityChange: event => YouTubePlayerBridge.sendPlaybackQualityChange(event.data), 29 | onPlaybackRateChange: event => YouTubePlayerBridge.sendPlaybackRateChange(event.data), 30 | onError: error => YouTubePlayerBridge.sendError(error.data), 31 | onApiChange: () => YouTubePlayerBridge.sendApiChange() 32 | }, 33 | playerVars: { 34 | autoplay: 0, 35 | autohide: 1, 36 | controls: 0, 37 | enablejsapi: 1, 38 | fs: 0, 39 | origin: 'https://www.youtube.com', 40 | rel: 0, 41 | showinfo: 0, 42 | iv_load_policy: 3 43 | } 44 | }) 45 | } 46 | 47 | function restoreCommunication() { 48 | YouTubePlayerBridge.sendYouTubeIframeAPIReady() 49 | sendPlayerStateChange(lastState) 50 | YouTubePlayerBridge.sendVideoId(lastVideoId) 51 | } 52 | 53 | function sendPlayerStateChange(playerState) { 54 | lastState = playerState 55 | 56 | let timerTaskId 57 | clearInterval(timerTaskId) 58 | 59 | switch (playerState) { 60 | case YT.PlayerState.UNSTARTED: 61 | sendStateChange(UNSTARTED) 62 | return 63 | 64 | case YT.PlayerState.ENDED: 65 | sendStateChange(ENDED) 66 | return 67 | 68 | case YT.PlayerState.PLAYING: 69 | sendStateChange(PLAYING) 70 | timerTaskId = setInterval( () => YouTubePlayerBridge.sendVideoCurrentTime( player.getCurrentTime() ), 100 ) 71 | sendVideoData(player) 72 | return 73 | 74 | case YT.PlayerState.PAUSED: 75 | sendStateChange(PAUSED) 76 | return 77 | 78 | case YT.PlayerState.BUFFERING: 79 | sendStateChange(BUFFERING) 80 | return 81 | 82 | case YT.PlayerState.CUED: 83 | sendStateChange(CUED) 84 | return 85 | } 86 | 87 | function sendVideoData(player) { 88 | const videoDuration = player.getDuration() 89 | YouTubePlayerBridge.sendVideoDuration(videoDuration) 90 | } 91 | 92 | function sendStateChange(newState) { 93 | YouTubePlayerBridge.sendStateChange(newState) 94 | } 95 | } 96 | 97 | // JAVA to WEB functions 98 | function seekTo(startSeconds) { 99 | player.seekTo(startSeconds, true) 100 | } 101 | 102 | function pauseVideo() { 103 | player.pauseVideo() 104 | } 105 | 106 | function playVideo() { 107 | player.playVideo() 108 | } 109 | 110 | function loadVideo(videoId, startSeconds) { 111 | lastVideoId = videoId 112 | 113 | player.loadVideoById(videoId, startSeconds) 114 | YouTubePlayerBridge.sendVideoId(videoId) 115 | } 116 | 117 | function cueVideo(videoId, startSeconds) { 118 | lastVideoId = videoId 119 | 120 | player.cueVideoById(videoId, startSeconds) 121 | YouTubePlayerBridge.sendVideoId(videoId) 122 | } 123 | 124 | function mute() { 125 | player.mute() 126 | } 127 | 128 | function unMute() { 129 | player.unMute() 130 | } 131 | 132 | function setVolume(volumePercent) { 133 | player.setVolume(volumePercent) 134 | } 135 | 136 | function getActions() { 137 | return actions 138 | } 139 | 140 | const actions = { seekTo, pauseVideo, playVideo, loadVideo, cueVideo, mute, unMute, setVolume } 141 | 142 | return { 143 | initialize, 144 | restoreCommunication, 145 | getActions 146 | } 147 | } 148 | 149 | export default YouTubePlayer -------------------------------------------------------------------------------- /chromecast-receiver/js/io/ChromecastCommunicationChannel.js: -------------------------------------------------------------------------------- 1 | import { isMessagePoperlyFormatted } from "./YouTubeMessage.js" 2 | 3 | function ChromecastCommunicationChannel(namespace) { 4 | const context = cast.framework.CastReceiverContext.getInstance() 5 | 6 | function sendMessage(data) { 7 | if(!isMessagePoperlyFormatted(data)) { 8 | console.error("object not properly formatted.") 9 | console.error(data) 10 | 11 | return; 12 | } 13 | 14 | context.getSenders().forEach( sender => context.sendCustomMessage(namespace, sender.id, data) ) 15 | } 16 | 17 | return { 18 | sendMessage: sendMessage 19 | } 20 | } 21 | 22 | export default ChromecastCommunicationChannel -------------------------------------------------------------------------------- /chromecast-receiver/js/io/SenderMessagesDispatcher.js: -------------------------------------------------------------------------------- 1 | const INIT_COMMUNICATION_CONSTANTS = "INIT_COMMUNICATION_CONSTANTS" 2 | 3 | /** 4 | * Class responsible for dispatching messages received from the cast sender. 5 | * @param {*} communicationConstants Constants used for sender-receiver communication. Sent by the cast sender with the INIT_COMMUNICATION_CONSTANTS message. 6 | * @param {*} callbacks 7 | */ 8 | function SenderMessagesDispatcher(communicationConstants, callbacks) { 9 | 10 | function onMessage(message) { 11 | console.log(message.data) 12 | 13 | if(message.data.command === INIT_COMMUNICATION_CONSTANTS) 14 | callbacks.onInitMessageReceived(message.data.communicationConstants) 15 | 16 | else if(message.data.command === communicationConstants.LOAD) 17 | callbacks.loadVideo(message.data.videoId, Number(message.data.startSeconds)) 18 | else if(message.data.command === communicationConstants.CUE) 19 | callbacks.cueVideo(message.data.videoId, Number(message.data.startSeconds)) 20 | else if(message.data.command === communicationConstants.PLAY) 21 | callbacks.playVideo() 22 | else if(message.data.command === communicationConstants.PAUSE) 23 | callbacks.pauseVideo() 24 | 25 | else if(message.data.command === communicationConstants.MUTE) 26 | callbacks.mute() 27 | else if(message.data.command === communicationConstants.UNMUTE) 28 | callbacks.unMute() 29 | else if(message.data.command === communicationConstants.SET_VOLUME) 30 | callbacks.setVolume(Number(message.data.volumePercent)) 31 | else if(message.data.command === communicationConstants.SEEK_TO) 32 | callbacks.seekTo(Number(message.data.time)) 33 | } 34 | 35 | return { 36 | onMessage 37 | } 38 | } 39 | 40 | export default SenderMessagesDispatcher -------------------------------------------------------------------------------- /chromecast-receiver/js/io/YouTubeMessage.js: -------------------------------------------------------------------------------- 1 | export function YouTubeMessage(type="", data = "") { 2 | return { type, data } 3 | } 4 | 5 | /** 6 | * Checkes if message is a YouTubeMessage or not. 7 | * @param {*} message 8 | */ 9 | export function isMessagePoperlyFormatted(message) { 10 | const sampleMessage = new YouTubeMessage(); 11 | 12 | for (const key in sampleMessage) 13 | if (!message.hasOwnProperty(key)) 14 | return false 15 | 16 | return true 17 | } -------------------------------------------------------------------------------- /chromecast-receiver/js/io/YouTubePlayerRemoteBridge.js: -------------------------------------------------------------------------------- 1 | import { YouTubeMessage } from "./YouTubeMessage.js" 2 | 3 | /** 4 | * Class responsible for sending messages to the cast sender 5 | * @param {*} communicationConstants 6 | * @param {*} communicationChannel 7 | */ 8 | function YouTubePlayerRemoteBridge(communicationConstants, communicationChannel) { 9 | 10 | function sendYouTubeIframeAPIReady() { 11 | communicationChannel.sendMessage(new YouTubeMessage(communicationConstants.IFRAME_API_READY)) 12 | } 13 | 14 | function sendReady() { 15 | communicationChannel.sendMessage(new YouTubeMessage(communicationConstants.READY)) 16 | } 17 | 18 | function sendStateChange(data) { 19 | communicationChannel.sendMessage(new YouTubeMessage(communicationConstants.STATE_CHANGED, data)) 20 | } 21 | 22 | function sendPlaybackQualityChange(data) { 23 | communicationChannel.sendMessage(new YouTubeMessage(communicationConstants.PLAYBACK_QUALITY_CHANGED, data)) 24 | } 25 | 26 | function sendPlaybackRateChange(data) { 27 | communicationChannel.sendMessage(new YouTubeMessage(communicationConstants.PLAYBACK_RATE_CHANGED, data)) 28 | } 29 | 30 | function sendError(data) { 31 | communicationChannel.sendMessage(new YouTubeMessage(communicationConstants.ERROR, data)) 32 | } 33 | 34 | function sendApiChange() { 35 | communicationChannel.sendMessage(new YouTubeMessage(communicationConstants.API_CHANGED)) 36 | } 37 | 38 | function sendVideoCurrentTime(data) { 39 | communicationChannel.sendMessage(new YouTubeMessage(communicationConstants.VIDEO_CURRENT_TIME, data)) 40 | } 41 | 42 | function sendVideoDuration(data) { 43 | communicationChannel.sendMessage(new YouTubeMessage(communicationConstants.VIDEO_DURATION, data)) 44 | } 45 | 46 | function sendVideoId(data) { 47 | communicationChannel.sendMessage(new YouTubeMessage(communicationConstants.VIDEO_ID, data)) 48 | } 49 | 50 | return { 51 | sendYouTubeIframeAPIReady: sendYouTubeIframeAPIReady, 52 | sendReady: sendReady, 53 | sendStateChange: sendStateChange, 54 | sendPlaybackQualityChange: sendPlaybackQualityChange, 55 | sendPlaybackRateChange: sendPlaybackRateChange, 56 | sendError: sendError, 57 | sendApiChange: sendApiChange, 58 | sendVideoCurrentTime: sendVideoCurrentTime, 59 | sendVideoDuration: sendVideoDuration, 60 | sendVideoId: sendVideoId, 61 | } 62 | } 63 | 64 | export default YouTubePlayerRemoteBridge -------------------------------------------------------------------------------- /chromecast-receiver/js/main.js: -------------------------------------------------------------------------------- 1 | import SenderMessagesDispatcher from "./io/SenderMessagesDispatcher.js" 2 | import ChromecastCommunicationChannel from "./io/ChromecastCommunicationChannel.js" 3 | import YouTubePlayer from "./YouTubePlayer.js" 4 | 5 | const namespace = "urn:x-cast:com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.communication" 6 | let communicationConstants = {} 7 | 8 | const context = cast.framework.CastReceiverContext.getInstance() 9 | 10 | const communicationChannel = new ChromecastCommunicationChannel(namespace) 11 | const youTubePlayer = new YouTubePlayer(communicationConstants, communicationChannel) 12 | const senderMessagesDispatcher = new SenderMessagesDispatcher(communicationConstants, { onInitMessageReceived, ...youTubePlayer.getActions() }) 13 | 14 | let isYouTubeIframeAPIReady = false 15 | 16 | context.addCustomMessageListener(namespace, senderMessagesDispatcher.onMessage) 17 | context.start() 18 | 19 | function onInitMessageReceived(parsedCommunicationConstants) { 20 | if(!isYouTubeIframeAPIReady) { 21 | initCommunicationConstants(parsedCommunicationConstants) 22 | loadYouTubeIFrameAPIs() 23 | } else 24 | youTubePlayer.restoreCommunication() 25 | } 26 | 27 | function initCommunicationConstants(parsedCommunicationConstants) { 28 | for (let key in parsedCommunicationConstants) 29 | communicationConstants[key] = parsedCommunicationConstants[key] 30 | } 31 | 32 | function loadYouTubeIFrameAPIs() { 33 | const script = document.createElement('script') 34 | script.src = "https://www.youtube.com/iframe_api" 35 | document.getElementsByTagName('head')[0].appendChild(script) 36 | } 37 | 38 | // called automatically by the IFrame APIs 39 | function onYouTubeIframeAPIReady() { 40 | isYouTubeIframeAPIReady = true 41 | youTubePlayer.initialize() 42 | } 43 | 44 | window.main_onYouTubeIframeAPIReady = onYouTubeIframeAPIReady -------------------------------------------------------------------------------- /chromecast-sender/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /chromecast-sender/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-android-extensions' 5 | apply plugin: 'org.jetbrains.dokka-android' 6 | 7 | ext { 8 | bintrayRepo = 'android-youtube-player' 9 | bintrayName = 'chromecast-sender' 10 | 11 | publishedGroupId = 'com.pierfrancescosoffritti.androidyoutubeplayer' 12 | libraryName = 'chromecast-youtube-player' 13 | artifact = 'chromecast-sender' 14 | 15 | libraryDescription = 'Extension for the android-youtube-player library, that enables Chromecast compatibility.' 16 | 17 | siteUrl = 'https://github.com/PierfrancescoSoffritti/chromecast-youtube-player' 18 | gitUrl = 'https://github.com/PierfrancescoSoffritti/chromecast-youtube-player.git' 19 | 20 | libraryVersion = '0.9' 21 | 22 | developerId = 'PierfrancescoSoffritti' 23 | developerName = 'Pierfrancesco Soffritti' 24 | developerEmail = 'soffritti.pierfrancesco@gmail.com' 25 | 26 | licenseName = 'MIT License' 27 | licenseUrl = 'https://github.com/PierfrancescoSoffritti/chromecast-youtube-player/blob/master/LICENSE' 28 | allLicenses = ["MIT"] 29 | } 30 | 31 | android { 32 | compileSdkVersion 27 33 | defaultConfig { 34 | minSdkVersion 17 35 | targetSdkVersion 27 36 | versionCode 1 37 | versionName "1.0" 38 | 39 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 40 | } 41 | buildTypes { 42 | release { 43 | minifyEnabled false 44 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 45 | } 46 | } 47 | } 48 | 49 | dokka { 50 | outputFormat = 'html' 51 | outputDirectory = "$buildDir/javadoc" 52 | } 53 | 54 | dependencies { 55 | implementation fileTree(dir: 'libs', include: ['*.jar']) 56 | testImplementation 'junit:junit:4.12' 57 | 58 | api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | api 'com.github.PierfrancescoSoffritti:AndroidYouTubePlayer:dev-SNAPSHOT' 60 | 61 | implementation 'com.android.support:appcompat-v7:27.1.1' 62 | implementation 'com.android.support:mediarouter-v7:27.1.1' 63 | implementation 'com.google.android.gms:play-services-cast-framework:15.0.1' 64 | } 65 | 66 | apply from: 'installAndDeployConfigFiles/install_v1.gradle' 67 | apply from: 'installAndDeployConfigFiles/bintray_v1.gradle' 68 | -------------------------------------------------------------------------------- /chromecast-sender/installAndDeployConfigFiles/bintray_v1.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | 3 | version = libraryVersion 4 | 5 | if (project.hasProperty("kotlin")) { // Kotlin libraries 6 | task sourcesJar(type: Jar) { 7 | classifier = 'sources' 8 | from android.sourceSets.main.java.srcDirs 9 | } 10 | 11 | task javadoc(type: Javadoc, dependsOn: dokka) { 12 | 13 | } 14 | } else if (project.hasProperty("android")) { // Android libraries 15 | task sourcesJar(type: Jar) { 16 | classifier = 'sources' 17 | from android.sourceSets.main.java.srcDirs 18 | } 19 | 20 | task javadoc(type: Javadoc) { 21 | source = android.sourceSets.main.java.srcDirs 22 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 23 | } 24 | } else { // Java libraries 25 | task sourcesJar(type: Jar, dependsOn: classes) { 26 | classifier = 'sources' 27 | from sourceSets.main.allSource 28 | } 29 | } 30 | 31 | task javadocJar(type: Jar, dependsOn: javadoc) { 32 | classifier = 'javadoc' 33 | from javadoc.destinationDir 34 | } 35 | 36 | artifacts { 37 | archives javadocJar 38 | archives sourcesJar 39 | } 40 | 41 | // Bintray 42 | Properties properties = new Properties() 43 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 44 | 45 | bintray { 46 | user = properties.getProperty("bintray.user") 47 | key = properties.getProperty("bintray.apikey") 48 | 49 | configurations = ['archives'] 50 | pkg { 51 | repo = bintrayRepo 52 | name = bintrayName 53 | desc = libraryDescription 54 | websiteUrl = siteUrl 55 | vcsUrl = gitUrl 56 | licenses = allLicenses 57 | publish = true 58 | publicDownloadNumbers = true 59 | version { 60 | desc = libraryDescription 61 | gpg { 62 | sign = false //Determines whether to GPG sign the files. The default is false 63 | passphrase = properties.getProperty("bintray.gpg.password") 64 | //Optional. The passphrase for GPG signing' 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /chromecast-sender/installAndDeployConfigFiles/install_v1.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | 3 | // Maven Group ID for the artifact 4 | group = publishedGroupId 5 | 6 | install { 7 | repositories.mavenInstaller { 8 | // This generates POM.xml with proper parameters 9 | pom { 10 | project { 11 | packaging 'aar' 12 | groupId publishedGroupId 13 | artifactId artifact 14 | 15 | // Add your description here 16 | name libraryName 17 | description libraryDescription 18 | url siteUrl 19 | 20 | // Set your license 21 | licenses { 22 | license { 23 | name licenseName 24 | url licenseUrl 25 | } 26 | } 27 | developers { 28 | developer { 29 | id developerId 30 | name developerName 31 | email developerEmail 32 | } 33 | } 34 | scm { 35 | connection gitUrl 36 | developerConnection gitUrl 37 | url siteUrl 38 | 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chromecast-sender/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /chromecast-sender/src/androidTest/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/ChromecastYouTubePlayer.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender 2 | 3 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure.ChromecastCommunicationChannel 4 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.youtube.ChromecastCommunicationConstants 5 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.youtube.ChromecastYouTubeMessageDispatcher 6 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.utils.JSONUtils 7 | import com.pierfrancescosoffritti.youtubeplayer.player.* 8 | import com.pierfrancescosoffritti.youtubeplayer.player.listeners.YouTubePlayerInitListener 9 | import com.pierfrancescosoffritti.youtubeplayer.player.listeners.YouTubePlayerListener 10 | 11 | class ChromecastYouTubePlayer internal constructor(private val chromecastCommunicationChannel: ChromecastCommunicationChannel) : YouTubePlayer, YouTubePlayerBridge.YouTubePlayerBridgeCallbacks { 12 | 13 | private lateinit var youTubePlayerInitListener: YouTubePlayerInitListener 14 | 15 | private val inputMessageDispatcher = ChromecastYouTubeMessageDispatcher(YouTubePlayerBridge(this)) 16 | private val youTubePlayerListeners = HashSet() 17 | 18 | internal fun initialize(initListener: YouTubePlayerInitListener) { 19 | youTubePlayerListeners.clear() 20 | 21 | youTubePlayerInitListener = initListener 22 | 23 | chromecastCommunicationChannel.addObserver(inputMessageDispatcher) 24 | } 25 | 26 | override fun onYouTubeIframeAPIReady() { 27 | youTubePlayerInitListener.onInitSuccess(this) 28 | } 29 | 30 | override fun loadVideo(videoId: String, startSeconds: Float) { 31 | val message = JSONUtils.buildFlatJson( 32 | "command" to ChromecastCommunicationConstants.LOAD, 33 | "videoId" to videoId, 34 | "startSeconds" to startSeconds.toString() 35 | ) 36 | 37 | chromecastCommunicationChannel.sendMessage(message) 38 | } 39 | 40 | override fun cueVideo(videoId: String, startSeconds: Float) { 41 | val message = JSONUtils.buildFlatJson( 42 | "command" to ChromecastCommunicationConstants.CUE, 43 | "videoId" to videoId, 44 | "startSeconds" to startSeconds.toString() 45 | ) 46 | 47 | chromecastCommunicationChannel.sendMessage(message) 48 | } 49 | 50 | override fun play() { 51 | val message = JSONUtils.buildFlatJson( 52 | "command" to ChromecastCommunicationConstants.PLAY 53 | ) 54 | 55 | chromecastCommunicationChannel.sendMessage(message) 56 | } 57 | 58 | override fun pause() { 59 | val message = JSONUtils.buildFlatJson( 60 | "command" to ChromecastCommunicationConstants.PAUSE 61 | ) 62 | 63 | chromecastCommunicationChannel.sendMessage(message) 64 | } 65 | 66 | override fun setVolume(volumePercent: Int) { 67 | val message = JSONUtils.buildFlatJson( 68 | "command" to ChromecastCommunicationConstants.SET_VOLUME, 69 | "volumePercent" to volumePercent.toString() 70 | ) 71 | 72 | chromecastCommunicationChannel.sendMessage(message) 73 | } 74 | 75 | override fun seekTo(time: Float) { 76 | val message = JSONUtils.buildFlatJson( 77 | "command" to ChromecastCommunicationConstants.SEEK_TO, 78 | "time" to time.toString() 79 | ) 80 | 81 | chromecastCommunicationChannel.sendMessage(message) 82 | } 83 | 84 | override fun addListener(listener: YouTubePlayerListener): Boolean = youTubePlayerListeners.add(listener) 85 | override fun removeListener(listener: YouTubePlayerListener): Boolean = youTubePlayerListeners.remove(listener) 86 | override fun getListeners(): MutableCollection = youTubePlayerListeners 87 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/ChromecastYouTubePlayerContext.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender 2 | 3 | import com.google.android.gms.cast.framework.SessionManager 4 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure.ChromecastConnectionListener 5 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure.ChromecastManager 6 | import com.pierfrancescosoffritti.youtubeplayer.player.listeners.YouTubePlayerInitListener 7 | 8 | class ChromecastYouTubePlayerContext(sessionManager: SessionManager, vararg chromecastConnectionListeners: ChromecastConnectionListener) : ChromecastConnectionListener { 9 | private val chromecastConnectionListeners = HashSet() 10 | 11 | private val chromecastManager = ChromecastManager(this, sessionManager, this.chromecastConnectionListeners) 12 | private val chromecastYouTubePlayer = ChromecastYouTubePlayer(chromecastManager.chromecastCommunicationChannel) 13 | 14 | private var chromecastConnected = false 15 | 16 | init { 17 | chromecastConnectionListeners.forEach { this.chromecastConnectionListeners.add(it) } 18 | onCreate() 19 | } 20 | 21 | // does this really have to be called when the activity is created? 22 | // @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) 23 | private fun onCreate() { 24 | chromecastManager.restoreSession() 25 | chromecastManager.addSessionManagerListener() 26 | } 27 | 28 | // @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) 29 | // private fun onResume() = chromecastManager.addSessionManagerListener() 30 | 31 | // if this is enabled the library can't know when a session is terminated (if the app is in the background), therefore it can't remove notifications etc. 32 | // @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) 33 | // private fun onPause() = chromecastManager.removeSessionManagerListener() 34 | 35 | fun initialize(youTubePlayerInitListener: YouTubePlayerInitListener) { 36 | if(!chromecastConnected) 37 | throw RuntimeException("ChromecastYouTubePlayerContext, can't initialize before Chromecast connection is established.") 38 | 39 | chromecastYouTubePlayer.initialize(youTubePlayerInitListener) 40 | } 41 | 42 | fun release() { 43 | endCurrentSession() 44 | chromecastManager.release() 45 | chromecastConnectionListeners.clear() 46 | } 47 | 48 | fun endCurrentSession() { 49 | chromecastManager.endCurrentSession() 50 | } 51 | 52 | override fun onChromecastConnecting() { 53 | } 54 | 55 | override fun onChromecastConnected(chromecastYouTubePlayerContext: ChromecastYouTubePlayerContext) { 56 | chromecastConnected = true 57 | } 58 | 59 | override fun onChromecastDisconnected() { 60 | chromecastConnected = false 61 | } 62 | 63 | fun addChromecastConnectionListener(chromecastConnectionListener: ChromecastConnectionListener) = chromecastConnectionListeners.add(chromecastConnectionListener) 64 | fun removeChromecastConnectionListener(chromecastConnectionListener: ChromecastConnectionListener) = chromecastConnectionListeners.remove(chromecastConnectionListener) 65 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/io/infrastructure/CastSessionListener.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure 2 | 3 | import com.google.android.gms.cast.framework.CastSession 4 | 5 | internal interface CastSessionListener { 6 | fun onCastSessionConnecting() 7 | fun onCastSessionConnected(castSession: CastSession) 8 | fun onCastSessionDisconnected(castSession: CastSession) 9 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/io/infrastructure/CastSessionManagerListener.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure 2 | 3 | import com.google.android.gms.cast.framework.CastSession 4 | import com.google.android.gms.cast.framework.SessionManagerListener 5 | 6 | internal class CastSessionManagerListener(private val castSessionListener: CastSessionListener) : SessionManagerListener { 7 | 8 | override fun onSessionEnding(castSession: CastSession) { } 9 | override fun onSessionSuspended(castSession: CastSession, p1: Int) { } 10 | 11 | override fun onSessionStarting(castSession: CastSession) = castSessionListener.onCastSessionConnecting() 12 | override fun onSessionResuming(castSession: CastSession, p1: String) = castSessionListener.onCastSessionConnecting() 13 | 14 | override fun onSessionEnded(castSession: CastSession, error: Int) = castSessionListener.onCastSessionDisconnected(castSession) 15 | override fun onSessionResumed(castSession: CastSession, wasSuspended: Boolean) = castSessionListener.onCastSessionConnected(castSession) 16 | override fun onSessionResumeFailed(castSession: CastSession, p1: Int) = castSessionListener.onCastSessionDisconnected(castSession) 17 | override fun onSessionStarted(castSession: CastSession, sessionId: String) = castSessionListener.onCastSessionConnected(castSession) 18 | override fun onSessionStartFailed(castSession: CastSession, p1: Int) = castSessionListener.onCastSessionConnected(castSession) 19 | 20 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/io/infrastructure/ChromecastCommunicationChannel.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure 2 | 3 | import com.google.android.gms.cast.Cast 4 | import com.google.android.gms.cast.CastDevice 5 | 6 | /** 7 | * Custom channel for full-duplex communication between sender and receiver, on a specific namespace. 8 | * 9 | * The channel can be observed with a [ChromecastChannelObserver] 10 | */ 11 | internal interface ChromecastCommunicationChannel : Cast.MessageReceivedCallback { 12 | val namespace: String 13 | val observers : HashSet 14 | 15 | fun sendMessage(message: String) 16 | override fun onMessageReceived(castDevice: CastDevice, namespace: String, message: String) 17 | 18 | fun addObserver(channelObserver: ChromecastChannelObserver) = observers.add(channelObserver) 19 | fun removeObserver(channelObserver: ChromecastChannelObserver) = observers.remove(channelObserver) 20 | 21 | /** 22 | * Implement this interface to observe a [ChromecastCommunicationChannel] 23 | */ 24 | interface ChromecastChannelObserver { 25 | fun onMessageReceived(messageFromReceiver: MessageFromReceiver) 26 | } 27 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/io/infrastructure/ChromecastConnectionListener.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure 2 | 3 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.ChromecastYouTubePlayerContext 4 | 5 | /** 6 | * Implement this interface to be notified about changes in the cast connection. 7 | */ 8 | interface ChromecastConnectionListener { 9 | fun onChromecastConnecting() 10 | fun onChromecastConnected(chromecastYouTubePlayerContext: ChromecastYouTubePlayerContext) 11 | fun onChromecastDisconnected() 12 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/io/infrastructure/ChromecastManager.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure 2 | 3 | import com.google.android.gms.cast.framework.CastSession 4 | import com.google.android.gms.cast.framework.SessionManager 5 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.ChromecastYouTubePlayerContext 6 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.utils.JSONUtils 7 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.youtube.ChromecastCommunicationConstants 8 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.youtube.ChromecastYouTubeIOChannel 9 | 10 | /** 11 | * Class responsible for chromecast sessions. 12 | */ 13 | internal class ChromecastManager( 14 | private val chromecastYouTubePlayerContext: ChromecastYouTubePlayerContext, 15 | private val sessionManager: SessionManager, 16 | private val chromecastConnectionListeners: Set) : CastSessionListener { 17 | 18 | val chromecastCommunicationChannel = ChromecastYouTubeIOChannel(sessionManager) 19 | private val castSessionManagerListener = CastSessionManagerListener(this) 20 | 21 | override fun onCastSessionConnecting() { 22 | chromecastConnectionListeners.forEach { it.onChromecastConnecting() } 23 | } 24 | 25 | override fun onCastSessionConnected(castSession: CastSession) { 26 | castSession.removeMessageReceivedCallbacks(chromecastCommunicationChannel.namespace) 27 | castSession.setMessageReceivedCallbacks(chromecastCommunicationChannel.namespace, chromecastCommunicationChannel) 28 | 29 | sendCommunicationConstants(chromecastCommunicationChannel) 30 | 31 | chromecastYouTubePlayerContext.onChromecastConnected(chromecastYouTubePlayerContext) 32 | chromecastConnectionListeners.forEach { it.onChromecastConnected(chromecastYouTubePlayerContext) } 33 | } 34 | 35 | override fun onCastSessionDisconnected(castSession: CastSession) { 36 | castSession.removeMessageReceivedCallbacks(chromecastCommunicationChannel.namespace) 37 | 38 | chromecastYouTubePlayerContext.onChromecastDisconnected() 39 | chromecastConnectionListeners.forEach { it.onChromecastDisconnected() } 40 | } 41 | 42 | fun restoreSession() { 43 | val currentCastSessions = sessionManager.currentCastSession 44 | if(currentCastSessions != null) 45 | onCastSessionConnected(currentCastSessions) 46 | } 47 | 48 | fun endCurrentSession() { 49 | sessionManager.endCurrentSession(true) 50 | } 51 | 52 | fun addSessionManagerListener() = sessionManager.addSessionManagerListener(castSessionManagerListener, CastSession::class.java) 53 | fun removeSessionManagerListener() = sessionManager.removeSessionManagerListener(castSessionManagerListener, CastSession::class.java) 54 | 55 | fun release() { 56 | removeSessionManagerListener() 57 | } 58 | 59 | private fun sendCommunicationConstants(chromecastCommunicationChannel: ChromecastCommunicationChannel) { 60 | val communicationConstants = ChromecastCommunicationConstants.asJson() 61 | 62 | val message = JSONUtils.buildCommunicationConstantsJson( 63 | "command" to ChromecastCommunicationConstants.INIT_COMMUNICATION_CONSTANTS, 64 | "communicationConstants" to communicationConstants 65 | ) 66 | 67 | chromecastCommunicationChannel.sendMessage(message) 68 | } 69 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/io/infrastructure/MessageFromReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure 2 | 3 | /** 4 | * POJO representing a message received from the cast receiver. 5 | */ 6 | internal data class MessageFromReceiver(val type: String, val data: String) -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/io/youtube/ChromecastCommunicationConstants.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.youtube 2 | 3 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.utils.JSONUtils 4 | 5 | /** 6 | * Set of constants used for sender-receiver communication. The sender will send the constants to the receiver when a connection is initiated. 7 | */ 8 | internal object ChromecastCommunicationConstants { 9 | // receiver to sender 10 | const val INIT_COMMUNICATION_CONSTANTS = "INIT_COMMUNICATION_CONSTANTS" 11 | 12 | const val IFRAME_API_READY = "IFRAME_API_READY" 13 | const val READY = "READY" 14 | const val STATE_CHANGED = "STATE_CHANGED" 15 | const val PLAYBACK_QUALITY_CHANGED = "PLAYBACK_QUALITY_CHANGED" 16 | const val PLAYBACK_RATE_CHANGED = "PLAYBACK_RATE_CHANGED" 17 | const val ERROR = "ERROR" 18 | const val API_CHANGED = "API_CHANGED" 19 | const val VIDEO_CURRENT_TIME = "VIDEO_CURRENT_TIME" 20 | const val VIDEO_DURATION = "VIDEO_DURATION" 21 | const val VIDEO_ID = "VIDEO_ID" 22 | 23 | // sender to receiver 24 | const val LOAD = "LOAD" 25 | const val CUE = "CUE" 26 | const val PLAY = "PLAY" 27 | const val PAUSE = "PAUSE" 28 | const val SET_VOLUME = "SET_VOLUME" 29 | const val SEEK_TO = "SEEK_TO" 30 | 31 | fun asJson() = JSONUtils.buildFlatJson( 32 | IFRAME_API_READY to IFRAME_API_READY, 33 | READY to READY, 34 | STATE_CHANGED to STATE_CHANGED, 35 | PLAYBACK_QUALITY_CHANGED to PLAYBACK_QUALITY_CHANGED, 36 | PLAYBACK_RATE_CHANGED to PLAYBACK_RATE_CHANGED, 37 | ERROR to ERROR, 38 | API_CHANGED to API_CHANGED, 39 | VIDEO_CURRENT_TIME to VIDEO_CURRENT_TIME, 40 | VIDEO_DURATION to VIDEO_DURATION, 41 | VIDEO_ID to VIDEO_ID, 42 | 43 | LOAD to LOAD, 44 | CUE to CUE, 45 | PLAY to PLAY, 46 | PAUSE to PAUSE, 47 | SET_VOLUME to SET_VOLUME, 48 | SEEK_TO to SEEK_TO 49 | ) 50 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/io/youtube/ChromecastYouTubeIOChannel.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.youtube 2 | 3 | import com.google.android.gms.cast.CastDevice 4 | import com.google.android.gms.cast.framework.SessionManager 5 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure.ChromecastCommunicationChannel 6 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.utils.JSONUtils 7 | 8 | /** 9 | * Communication channel used to exchange messages with the YouTube Chromecast receiver. 10 | */ 11 | internal class ChromecastYouTubeIOChannel(private val sessionManager: SessionManager) : ChromecastCommunicationChannel { 12 | override val namespace get() = "urn:x-cast:com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.communication" 13 | 14 | override val observers = HashSet() 15 | 16 | override fun sendMessage(message: String) { 17 | try { 18 | sessionManager.currentCastSession 19 | .sendMessage(namespace, message) 20 | // .setResultCallback { 21 | // if(it.isSuccess) 22 | // Log.d(this.javaClass.simpleName, "message sent") 23 | // else 24 | // Log.e(this.javaClass.simpleName, "failed, can't send message") 25 | // } 26 | 27 | } catch (e: Exception) { 28 | throw RuntimeException(e) 29 | } 30 | } 31 | 32 | override fun onMessageReceived(castDevice: CastDevice, namespace: String, message: String) { 33 | val parsedMessage = JSONUtils.parseMessageFromReceiverJson(message) 34 | observers.forEach{ it.onMessageReceived(parsedMessage) } 35 | } 36 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/io/youtube/ChromecastYouTubeMessageDispatcher.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.youtube 2 | 3 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure.ChromecastCommunicationChannel 4 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure.MessageFromReceiver 5 | import com.pierfrancescosoffritti.youtubeplayer.player.YouTubePlayerBridge 6 | 7 | /** 8 | * Class responsible for dispatching messages received from the cast receiver. 9 | */ 10 | internal class ChromecastYouTubeMessageDispatcher(private val bridge: YouTubePlayerBridge) : ChromecastCommunicationChannel.ChromecastChannelObserver { 11 | override fun onMessageReceived(messageFromReceiver: MessageFromReceiver) { 12 | when (messageFromReceiver.type) { 13 | ChromecastCommunicationConstants.IFRAME_API_READY -> bridge.sendYouTubeIframeAPIReady() 14 | ChromecastCommunicationConstants.READY -> bridge.sendReady() 15 | ChromecastCommunicationConstants.STATE_CHANGED -> bridge.sendStateChange(messageFromReceiver.data) 16 | ChromecastCommunicationConstants.PLAYBACK_QUALITY_CHANGED -> bridge.sendPlaybackQualityChange(messageFromReceiver.data) 17 | ChromecastCommunicationConstants.PLAYBACK_RATE_CHANGED -> bridge.sendPlaybackRateChange(messageFromReceiver.data) 18 | ChromecastCommunicationConstants.ERROR -> bridge.sendError(messageFromReceiver.data) 19 | ChromecastCommunicationConstants.API_CHANGED -> bridge.sendApiChange() 20 | ChromecastCommunicationConstants.VIDEO_CURRENT_TIME -> bridge.sendVideoCurrentTime(messageFromReceiver.data) 21 | ChromecastCommunicationConstants.VIDEO_DURATION -> bridge.sendVideoDuration(messageFromReceiver.data) 22 | ChromecastCommunicationConstants.VIDEO_ID -> bridge.sendVideoId(messageFromReceiver.data) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/utils/JSONUtils.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.utils 2 | 3 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure.MessageFromReceiver 4 | 5 | /** 6 | * Utility class to read and parse JSON messages exchanged between sender and receiver. 7 | * The format of the messages is basic, no external library is needed. 8 | */ 9 | internal object JSONUtils { 10 | fun buildFlatJson(vararg args: Pair) : String { 11 | val jsonBuilder = StringBuilder("{") 12 | args.forEach { jsonBuilder.append("\"${it.first}\": \"${it.second}\",") } 13 | jsonBuilder.deleteCharAt(jsonBuilder.length-1) 14 | jsonBuilder.append("}") 15 | 16 | return jsonBuilder.toString() 17 | } 18 | 19 | fun buildCommunicationConstantsJson(command: Pair, communicationConstants: Pair) : String { 20 | val jsonBuilder = StringBuilder("{") 21 | jsonBuilder.append("\"${command.first}\": \"${command.second}\",") 22 | jsonBuilder.append("\"${communicationConstants.first}\": ${communicationConstants.second}") 23 | jsonBuilder.append("}") 24 | 25 | return jsonBuilder.toString() 26 | } 27 | 28 | fun parseMessageFromReceiverJson(json: String) : MessageFromReceiver { 29 | val strings = json.split(",") 30 | val values = strings.map { it.split(":")[1].trim().replace("\"", "").replace("{", "").replace("}", "") } 31 | 32 | return MessageFromReceiver(values[0], values[1]) 33 | } 34 | } -------------------------------------------------------------------------------- /chromecast-sender/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | chromecast-sender 3 | 4 | -------------------------------------------------------------------------------- /chromecast-sender/src/test/java/com/pierfrancescosoffritti/androidyoutubeplayer/chromecast/chromecastsender/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PierfrancescoSoffritti/chromecast-youtube-player/2197bbb16f56fbda27ac5e8c16a8a0e8b1bd7631/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jun 06 14:14:08 CEST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /sample-app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample-app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-android-extensions' 5 | 6 | android { 7 | compileSdkVersion 27 8 | defaultConfig { 9 | applicationId "com.pierfrancescosoffritti.cyplayersample" 10 | minSdkVersion 17 11 | targetSdkVersion 27 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation fileTree(dir: 'libs', include: ['*.jar']) 26 | testImplementation 'junit:junit:4.12' 27 | 28 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 29 | 30 | implementation project(':chromecast-sender') 31 | implementation 'com.android.support:appcompat-v7:27.1.1' 32 | implementation 'com.android.support:design:27.1.1' 33 | implementation 'com.android.support:palette-v7:27.1.1' 34 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 35 | 36 | implementation 'com.getkeepsafe.taptargetview:taptargetview:1.11.0' 37 | 38 | implementation 'com.android.support:mediarouter-v7:27.1.1' 39 | implementation 'com.google.android.gms:play-services-cast-framework:15.0.1' 40 | implementation 'com.github.PierfrancescoSoffritti:AndroidYouTubePlayer:dev-SNAPSHOT' 41 | 42 | implementation "io.reactivex.rxjava2:rxjava:2.1.6" 43 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' 44 | implementation('com.google.api-client:google-api-client-android:1.23.0') { 45 | exclude group: 'org.apache.httpcomponents' 46 | } 47 | implementation('com.google.apis:google-api-services-youtube:v3-rev187-1.23.0') { 48 | exclude group: 'org.apache.httpcomponents' 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /sample-app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /sample-app/src/androidTest/java/com/pierfrancescosoffritti/cyplayersample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.cyplayersample; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /sample-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 27 | 28 | 32 | 33 | 37 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /sample-app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PierfrancescoSoffritti/chromecast-youtube-player/2197bbb16f56fbda27ac5e8c16a8a0e8b1bd7631/sample-app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /sample-app/src/main/java/com/pierfrancescosoffritti/cyplayersample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.cyplayersample 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.graphics.Point 7 | import android.net.Uri 8 | import android.os.Build 9 | import android.support.v7.app.AppCompatActivity 10 | import android.os.Bundle 11 | import android.support.design.widget.NavigationView 12 | import android.support.v4.view.GravityCompat 13 | import android.view.Menu 14 | import android.view.MenuItem 15 | import android.view.View 16 | import android.view.WindowManager 17 | import android.webkit.WebView 18 | import android.webkit.WebViewClient 19 | import com.getkeepsafe.taptargetview.TapTarget 20 | import com.getkeepsafe.taptargetview.TapTargetView 21 | import com.pierfrancescosoffritti.cyplayersample.examples.basicExample.BasicExampleActivity 22 | import com.pierfrancescosoffritti.cyplayersample.examples.localPlayerExample.LocalPlayerInitExampleActivity 23 | import com.pierfrancescosoffritti.cyplayersample.examples.notificationExample.NotificationExampleActivity 24 | import com.pierfrancescosoffritti.cyplayersample.examples.playerControlsExample.PlayerControlsExample 25 | import kotlinx.android.synthetic.main.activity_main.* 26 | 27 | class MainActivity : AppCompatActivity() { 28 | 29 | private var selectedMenuItem: MenuItem? = null 30 | 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | setContentView(R.layout.activity_main) 34 | 35 | initWebView() 36 | 37 | adjustStatusBarTranslucency() 38 | initToolbar() 39 | initNavDrawer() 40 | 41 | showFeatureDiscovery() 42 | } 43 | 44 | public override fun onResume() { 45 | super.onResume() 46 | selectedMenuItem?.isChecked = false 47 | } 48 | 49 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 50 | menuInflater.inflate(R.menu.main_activity_menu, menu) 51 | return true 52 | } 53 | 54 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 55 | when (item.itemId) { 56 | android.R.id.home -> { 57 | drawer_layout.openDrawer(GravityCompat.START) 58 | return true 59 | } 60 | R.id.open_on_github -> { 61 | startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/PierfrancescoSoffritti/chromecast-youtube-player"))) 62 | return true 63 | } 64 | } 65 | return super.onOptionsItemSelected(item) 66 | } 67 | 68 | override fun onBackPressed() { 69 | when { 70 | drawer_layout.isDrawerOpen(GravityCompat.START) -> drawer_layout.closeDrawer(GravityCompat.START) 71 | main_activity_webview.canGoBack() -> main_activity_webview.goBack() 72 | else -> super.onBackPressed() 73 | } 74 | } 75 | 76 | @SuppressLint("SetJavaScriptEnabled") 77 | private fun initWebView() { 78 | main_activity_webview.settings.javaScriptEnabled = true 79 | main_activity_webview.loadUrl("https://pierfrancescosoffritti.github.io/Android-YouTube-Player/") 80 | 81 | main_activity_webview.webViewClient = object : WebViewClient() { 82 | override fun onPageCommitVisible(view: WebView, url: String) { 83 | super.onPageCommitVisible(view, url) 84 | progressbar.visibility = View.GONE 85 | } 86 | 87 | override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean { 88 | return if (url != null && (url.startsWith("http://") || url.startsWith("https://"))) { 89 | startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) 90 | true 91 | } else 92 | false 93 | } 94 | } 95 | } 96 | 97 | private fun adjustStatusBarTranslucency() { 98 | if (Build.VERSION.SDK_INT >= 21) { 99 | val window = window 100 | val windowParams = window.attributes 101 | windowParams.flags = windowParams.flags or WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 102 | window.attributes = windowParams 103 | } 104 | } 105 | 106 | private fun initToolbar() { 107 | setSupportActionBar(toolbar) 108 | 109 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) 110 | supportActionBar!!.setHomeAsUpIndicator(R.drawable.ic_nav_drawer_menu_24dp) 111 | } 112 | 113 | private fun initNavDrawer() { 114 | setNavigationViewWidth(navigation_view) 115 | 116 | navigation_view.setNavigationItemSelectedListener { menuItem -> 117 | menuItem.isChecked = true 118 | selectedMenuItem = menuItem 119 | 120 | drawer_layout.closeDrawers() 121 | 122 | if (menuItem.itemId == R.id.open_base_example_menu_item) { 123 | val intent = Intent(this, BasicExampleActivity::class.java) 124 | startActivity(intent) 125 | } else if(menuItem.itemId == R.id.open_player_controls_example_menu_item) { 126 | val intent = Intent(this, PlayerControlsExample::class.java) 127 | startActivity(intent) 128 | } else if(menuItem.itemId == R.id.open_notification_example_menu_item) { 129 | val intent = Intent(this, NotificationExampleActivity::class.java) 130 | startActivity(intent) 131 | } else if(menuItem.itemId == R.id.open_local_player_example_menu_item) { 132 | val intent = Intent(this, LocalPlayerInitExampleActivity::class.java) 133 | startActivity(intent) 134 | } else if (menuItem.itemId == R.id.star_on_github) 135 | startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/PierfrancescoSoffritti/chromecast-youtube-player/stargazers"))) 136 | // else if (menuItem.getItemId() === R.id.rate_on_playstore) { 137 | // val appPackageName = packageName 138 | // try { 139 | // startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$appPackageName"))) 140 | // } catch (exception: ActivityNotFoundException) { 141 | // startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$appPackageName"))) 142 | // } 143 | // 144 | // } 145 | 146 | true 147 | } 148 | } 149 | 150 | private fun setNavigationViewWidth(navigationView: NavigationView) { 151 | val params = navigationView.layoutParams 152 | val width = getScreenWidth() - getToolbarHeight() 153 | val _320dp = resources.getDimensionPixelSize(R.dimen._320dp) 154 | params.width = if (width > _320dp) _320dp else width 155 | navigationView.layoutParams = params 156 | } 157 | 158 | private fun getToolbarHeight(): Int { 159 | return toolbar.layoutParams.height 160 | } 161 | 162 | private fun getScreenWidth(): Int { 163 | val display = windowManager.defaultDisplay 164 | val size = Point() 165 | display.getSize(size) 166 | return size.x 167 | } 168 | 169 | 170 | private fun showFeatureDiscovery() { 171 | val preferenceKey = "featureDiscoveryShown" 172 | val sharedPreferencesKey = "sampleApp_MainActivity_SharedPreferences" 173 | val prefs = getSharedPreferences(sharedPreferencesKey, Context.MODE_PRIVATE) 174 | val featureDiscoveryShown = prefs.getBoolean(preferenceKey, false) 175 | 176 | if (featureDiscoveryShown) 177 | return 178 | else 179 | prefs.edit().putBoolean(preferenceKey, true).apply() 180 | 181 | val target = toolbar.getChildAt(1) 182 | 183 | TapTargetView.showFor( 184 | this, 185 | TapTarget.forView(target, getString(R.string.explore_examples), getString(R.string.explore_examples_description)) 186 | .outerCircleColor(R.color.github_black) 187 | .outerCircleAlpha(1f) 188 | .targetCircleColor(android.R.color.white) 189 | .titleTextColor(android.R.color.white) 190 | .drawShadow(true) 191 | .transparentTarget(true), object : TapTargetView.Listener() { 192 | override fun onTargetClick(view: TapTargetView) { 193 | super.onTargetClick(view) 194 | target.performClick() 195 | } 196 | }) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/pierfrancescosoffritti/cyplayersample/examples/CastOptionsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.cyplayersample.examples 2 | 3 | import android.content.Context 4 | import com.google.android.gms.cast.framework.CastOptions 5 | import com.google.android.gms.cast.framework.SessionProvider 6 | import com.google.android.gms.cast.framework.OptionsProvider 7 | 8 | /** 9 | * Class providing setup info to the Chromecast framework, declared in manifest file. 10 | * 11 | * [see doc here](https://developers.google.com/cast/docs/android_sender_integrate#initialize_the_cast_context) 12 | */ 13 | internal class CastOptionsProvider : OptionsProvider { 14 | // This is the receiver id of the sample receiver. 15 | // Remember to change it with the ID of your own receiver. See documentation for more info. 16 | private val receiverId = "C5CBE8CA" 17 | 18 | override fun getCastOptions(appContext: Context): CastOptions { 19 | return CastOptions.Builder() 20 | .setReceiverApplicationId(receiverId) 21 | .build() 22 | } 23 | 24 | override fun getAdditionalSessionProviders(context: Context): List? { 25 | return null 26 | } 27 | } -------------------------------------------------------------------------------- /sample-app/src/main/java/com/pierfrancescosoffritti/cyplayersample/examples/basicExample/BasicExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.cyplayersample.examples.basicExample 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.support.v7.app.AppCompatActivity 6 | import android.util.Log 7 | import com.google.android.gms.cast.framework.CastContext 8 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.ChromecastYouTubePlayerContext 9 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure.ChromecastConnectionListener 10 | import com.pierfrancescosoffritti.cyplayersample.R 11 | import com.pierfrancescosoffritti.cyplayersample.utils.MediaRouteButtonUtils 12 | import com.pierfrancescosoffritti.cyplayersample.utils.PlayServicesUtils 13 | import com.pierfrancescosoffritti.cyplayersample.utils.PlaybackUtils 14 | import com.pierfrancescosoffritti.youtubeplayer.player.listeners.AbstractYouTubePlayerListener 15 | import com.pierfrancescosoffritti.youtubeplayer.player.listeners.YouTubePlayerInitListener 16 | import kotlinx.android.synthetic.main.activity_basic_example.* 17 | 18 | class BasicExampleActivity : AppCompatActivity() { 19 | 20 | private val googlePlayServicesAvailabilityRequestCode = 1 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_basic_example) 25 | 26 | MediaRouteButtonUtils.initMediaRouteButton(media_route_button) 27 | 28 | // can't use CastContext until I'm sure the user has GooglePlayServices 29 | PlayServicesUtils.checkGooglePlayServicesAvailability(this, googlePlayServicesAvailabilityRequestCode) { initChromecast() } 30 | } 31 | 32 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 33 | super.onActivityResult(requestCode, resultCode, data) 34 | 35 | // can't use CastContext until I'm sure the user has GooglePlayServices 36 | if(requestCode == googlePlayServicesAvailabilityRequestCode) 37 | PlayServicesUtils.checkGooglePlayServicesAvailability(this, googlePlayServicesAvailabilityRequestCode) {initChromecast()} 38 | } 39 | 40 | private fun initChromecast() { 41 | ChromecastYouTubePlayerContext(CastContext.getSharedInstance(this).sessionManager, SimpleChromecastConnectionListener()) 42 | } 43 | 44 | inner class SimpleChromecastConnectionListener: ChromecastConnectionListener { 45 | override fun onChromecastConnecting() { 46 | Log.d(javaClass.simpleName, "onChromecastConnecting") 47 | } 48 | 49 | override fun onChromecastConnected(chromecastYouTubePlayerContext: ChromecastYouTubePlayerContext) { 50 | Log.d(javaClass.simpleName, "onChromecastConnected") 51 | 52 | initializeCastPlayer(chromecastYouTubePlayerContext) 53 | } 54 | 55 | override fun onChromecastDisconnected() { 56 | Log.d(javaClass.simpleName, "onChromecastDisconnected") 57 | } 58 | 59 | private fun initializeCastPlayer(chromecastYouTubePlayerContext: ChromecastYouTubePlayerContext) { 60 | chromecastYouTubePlayerContext.initialize( YouTubePlayerInitListener { youtubePlayer -> 61 | 62 | youtubePlayer.addListener(object: AbstractYouTubePlayerListener() { 63 | override fun onReady() { 64 | youtubePlayer.loadVideo(PlaybackUtils.getNextVideoId(), 0f) 65 | } 66 | }) 67 | 68 | }) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /sample-app/src/main/java/com/pierfrancescosoffritti/cyplayersample/examples/localPlayerExample/LocalPlayerInitExampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.cyplayersample.examples.localPlayerExample 2 | 3 | import android.content.Intent 4 | import android.content.IntentFilter 5 | import android.os.Bundle 6 | import android.support.v7.app.AppCompatActivity 7 | import android.support.v7.app.MediaRouteButton 8 | import android.view.View 9 | import com.google.android.gms.cast.framework.CastContext 10 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.ChromecastYouTubePlayerContext 11 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure.ChromecastConnectionListener 12 | import com.pierfrancescosoffritti.cyplayersample.R 13 | import com.pierfrancescosoffritti.cyplayersample.utils.MediaRouteButtonUtils 14 | import com.pierfrancescosoffritti.cyplayersample.utils.PlayServicesUtils 15 | import com.pierfrancescosoffritti.cyplayersample.notifications.NotificationManager 16 | import com.pierfrancescosoffritti.cyplayersample.notifications.PlaybackControllerBroadcastReceiver 17 | import kotlinx.android.synthetic.main.local_and_cast_player_example.* 18 | 19 | 20 | class LocalPlayerInitExampleActivity : AppCompatActivity(), YouTubePlayersManager.LocalYouTubePlayerInitListener, ChromecastConnectionListener { 21 | private val googlePlayServicesAvailabilityRequestCode = 1 22 | 23 | private lateinit var youTubePlayersManager: YouTubePlayersManager 24 | private lateinit var mediaRouteButton : MediaRouteButton 25 | 26 | private lateinit var notificationManager: NotificationManager 27 | private lateinit var playbackControllerBroadcastReceiver: PlaybackControllerBroadcastReceiver 28 | 29 | private var connectedToChromecast = false 30 | 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | setContentView(R.layout.local_and_cast_player_example) 34 | 35 | lifecycle.addObserver(youtube_player_view) 36 | 37 | notificationManager = NotificationManager(this, LocalPlayerInitExampleActivity::class.java) 38 | 39 | youTubePlayersManager = YouTubePlayersManager(this, youtube_player_view, chromecast_controls_root, notificationManager) 40 | mediaRouteButton = MediaRouteButtonUtils.initMediaRouteButton(this) 41 | 42 | registerBroadcastReceiver() 43 | 44 | // can't use CastContext until I'm sure the user has GooglePlayServices 45 | PlayServicesUtils.checkGooglePlayServicesAvailability(this, googlePlayServicesAvailabilityRequestCode) { initChromecast() } 46 | } 47 | 48 | override fun onDestroy() { 49 | super.onDestroy() 50 | applicationContext.unregisterReceiver(playbackControllerBroadcastReceiver) 51 | } 52 | 53 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 54 | super.onActivityResult(requestCode, resultCode, data) 55 | 56 | // can't use CastContext until I'm sure the user has GooglePlayServices 57 | if(requestCode == googlePlayServicesAvailabilityRequestCode) 58 | PlayServicesUtils.checkGooglePlayServicesAvailability(this, googlePlayServicesAvailabilityRequestCode) {initChromecast()} 59 | } 60 | 61 | private fun initChromecast() { 62 | ChromecastYouTubePlayerContext( 63 | CastContext.getSharedInstance(this).sessionManager, 64 | this, playbackControllerBroadcastReceiver, youTubePlayersManager 65 | ) 66 | } 67 | 68 | override fun onChromecastConnecting() { 69 | } 70 | 71 | override fun onChromecastConnected(chromecastYouTubePlayerContext: ChromecastYouTubePlayerContext) { 72 | connectedToChromecast = true 73 | 74 | updateUI(true) 75 | 76 | notificationManager.showNotification() 77 | } 78 | 79 | override fun onChromecastDisconnected() { 80 | connectedToChromecast = false 81 | 82 | updateUI(false) 83 | 84 | notificationManager.dismissNotification() 85 | } 86 | 87 | override fun onLocalYouTubePlayerInit() { 88 | if(connectedToChromecast) 89 | return 90 | 91 | MediaRouteButtonUtils.addMediaRouteButtonToPlayerUI( 92 | mediaRouteButton, android.R.color.white, 93 | null, localPlayerUIMediaRouteButtonContainer 94 | ) 95 | } 96 | 97 | private fun registerBroadcastReceiver() { 98 | playbackControllerBroadcastReceiver = PlaybackControllerBroadcastReceiver({ youTubePlayersManager.togglePlayback() }) 99 | val filter = IntentFilter(PlaybackControllerBroadcastReceiver.TOGGLE_PLAYBACK) 100 | filter.addAction(PlaybackControllerBroadcastReceiver.STOP_CAST_SESSION) 101 | applicationContext.registerReceiver(playbackControllerBroadcastReceiver, filter) 102 | } 103 | 104 | private fun updateUI(connected: Boolean) { 105 | 106 | val disabledContainer = if(connected) localPlayerUIMediaRouteButtonContainer else chromecastPlayerUIMediaRouteButtonContainer 107 | val enabledContainer = if(connected) chromecastPlayerUIMediaRouteButtonContainer else localPlayerUIMediaRouteButtonContainer 108 | val mediaRouteButtonColor = if(connected) android.R.color.black else android.R.color.white 109 | 110 | // the media route button has a single instance. 111 | // therefore it has to be moved from the local YouTube player UI to the chromecast YouTube player UI, and vice versa. 112 | MediaRouteButtonUtils.addMediaRouteButtonToPlayerUI( 113 | mediaRouteButton, mediaRouteButtonColor, 114 | disabledContainer, enabledContainer 115 | ) 116 | 117 | youtube_player_view.visibility = if(connected) View.GONE else View.VISIBLE 118 | chromecast_controls_root.visibility = if(connected) View.VISIBLE else View.GONE 119 | } 120 | 121 | private val chromecastPlayerUIMediaRouteButtonContainer = object: MediaRouteButtonContainer { 122 | override fun addMediaRouteButton(mediaRouteButton: MediaRouteButton) = youTubePlayersManager.chromecastUIController.addView(mediaRouteButton) 123 | override fun removeMediaRouteButton(mediaRouteButton: MediaRouteButton) = youTubePlayersManager.chromecastUIController.removeView(mediaRouteButton) 124 | } 125 | 126 | private val localPlayerUIMediaRouteButtonContainer = object: MediaRouteButtonContainer { 127 | override fun addMediaRouteButton(mediaRouteButton: MediaRouteButton) = youtube_player_view.playerUIController.addView(mediaRouteButton) 128 | override fun removeMediaRouteButton(mediaRouteButton: MediaRouteButton) = youtube_player_view.playerUIController.removeView(mediaRouteButton) 129 | } 130 | } -------------------------------------------------------------------------------- /sample-app/src/main/java/com/pierfrancescosoffritti/cyplayersample/examples/localPlayerExample/MediaRouteButtonContainer.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.cyplayersample.examples.localPlayerExample 2 | 3 | import android.support.v7.app.MediaRouteButton 4 | 5 | interface MediaRouteButtonContainer { 6 | fun addMediaRouteButton(mediaRouteButton: MediaRouteButton) 7 | fun removeMediaRouteButton(mediaRouteButton: MediaRouteButton) 8 | } -------------------------------------------------------------------------------- /sample-app/src/main/java/com/pierfrancescosoffritti/cyplayersample/examples/localPlayerExample/YouTubePlayersManager.kt: -------------------------------------------------------------------------------- 1 | package com.pierfrancescosoffritti.cyplayersample.examples.localPlayerExample 2 | 3 | import android.view.View 4 | import android.widget.Button 5 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.ChromecastYouTubePlayerContext 6 | import com.pierfrancescosoffritti.androidyoutubeplayer.chromecast.chromecastsender.io.infrastructure.ChromecastConnectionListener 7 | import com.pierfrancescosoffritti.cyplayersample.R 8 | import com.pierfrancescosoffritti.cyplayersample.ui.SimpleChromecastUIController 9 | import com.pierfrancescosoffritti.cyplayersample.utils.PlaybackUtils 10 | import com.pierfrancescosoffritti.youtubeplayer.player.* 11 | import com.pierfrancescosoffritti.youtubeplayer.player.listeners.AbstractYouTubePlayerListener 12 | import com.pierfrancescosoffritti.youtubeplayer.player.listeners.YouTubePlayerInitListener 13 | import com.pierfrancescosoffritti.youtubeplayer.player.listeners.YouTubePlayerListener 14 | import com.pierfrancescosoffritti.youtubeplayer.utils.YouTubePlayerStateTracker 15 | 16 | /** 17 | * Class used to manage the two YouTubePlayers, local and cast. 18 | * 19 | * The local YouTubePlayer is supposed to stop playing when the cast player stars and vice versa. 20 | * 21 | * When one of the two players stops, the other has to resume the playback from where the previous player stopped. 22 | */ 23 | class YouTubePlayersManager( 24 | localYouTubePlayerInitListener: LocalYouTubePlayerInitListener, 25 | private val youtubePlayerView: YouTubePlayerView, chromecastControls: View, 26 | private val chromecastPlayerListener: YouTubePlayerListener) : ChromecastConnectionListener { 27 | 28 | private val nextVideoButton = chromecastControls.findViewById