-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
2 changed files
with
183 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# Cast PAL Basic Sample App | ||
|
||
This is a sample Cast custom web receiver app for testing Cast PAL. Follow the | ||
below instructions to set up the app, assuming your laptop and Cast device are | ||
on the same WiFi network and can connect to each other | ||
|
||
1. [Set up your Cast device for development] | ||
|
||
1. [Register a new Cast application] | ||
|
||
1. Follow the instructions for a "Custom Receiver". | ||
1. For the "Receiver Application URL" field, enter | ||
`http://[YOUR.LAPTOP.LOCAL.IP.ADDRESS]:8000/app.html`. | ||
1. Don't bother with the steps necessary to publish the application. | ||
1. Copy the application ID for later. | ||
|
||
1. Start a development server on your laptop. | ||
|
||
1. Run `python3 -m http.server 8000` in this folder, to start a simple | ||
static server. | ||
1. Test that everything is working properly by navigating to | ||
`http://[YOUR.LAPTOP.LOCAL.IP.ADDRESS]:8000/app.html` in your laptop | ||
browser. You should see the receiver app (it will not work properly | ||
though since it's not running on Cast). | ||
|
||
1. Navigate to [Cactool] in your laptop browser to start the custom receiver | ||
app on your Cast device. | ||
|
||
1. Paste the application ID into the "Receiver App ID" field in the "Cast | ||
Connect & Logger Options" tab. | ||
1. If your Cast device is recognized by your laptop (i.e. same Wifi network | ||
and the device was properly registed for development), you should see a | ||
Cast icon in the top left of the page by the logo. Consult the | ||
[Cactool documentation] if you're not seeing this. | ||
1. Click this button to cast, and the receiver app should load onto the | ||
Cast device. | ||
1. In your laptop browser, navigate to `chrome://inspect`, and you should | ||
be able to inspect the Cast device to see console messages and network | ||
requests. | ||
|
||
1. Load a media object in [Cactool] to trigger a nonce request. | ||
|
||
1. In [Cactool], go to the "Load Media" tab, and in the "Custom Load | ||
Request" panel, select the "LOAD" request type radio button. | ||
1. In the textarea containing the custom load request JSON, paste the | ||
following object:``` | ||
{ | ||
"media": { | ||
"contentId": "bbb", | ||
"contentUrl": "https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/mp4/BigBuckBunny.mp4", | ||
"entity": "myapp://playlist/1", | ||
"streamType": "BUFFERED", | ||
"vmapAdsRequest": { | ||
"adTagURL": "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostpod&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator=" | ||
} | ||
} | ||
} | ||
``` | ||
1. Click the "Send Request" button, and you should shortly see Big Buck | ||
Bunny playing on the Cast device with periodic VMAP ads interspersed. | ||
1. At the same time, in the Network tab, you should see that the | ||
`paln=[NONCE.GOES.HERE]` URL parameter was attached to the ad request. | ||
1. Observe and interact with playback to trigger PAL lifecycle events. | ||
1. Once playback has started, you should see a ping for the `playbackStart` | ||
event. | ||
1. In [Cactool], go to the "Media Control" tab, and pause, resume, and | ||
seek. These interactions should trigger pings for the `adTouch` event. | ||
1. When playback ends (from error, quitting, or finishing), you should see | ||
a ping for the `playbackEnd` event. | ||
[Set up your Cast device for development]: https://developers.google.com/cast/docs/registration#devices | ||
[Cast Developer Console]: https://cast.google.com/publish/#/overview | ||
[Charles Proxy]: https://sites.google.com/corp/google.com/charles-proxy/charles-proxy-home | ||
[Register a new Cast application]:https://developers.google.com/cast/docs/registration | ||
[SrcFS on Mac]: https://g3doc.corp.google.com/devtools/citc/g3doc/mac.md | ||
[Cactool]: https://casttool.appspot.com/cactool/ | ||
[Cactool documentation]: https://developers.google.com/cast/docs/debugging/cac_tool |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<html> | ||
<head> | ||
<script | ||
src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script> | ||
<script src="//imasdk.googleapis.com/pal/sdkloader/cast_pal.js"></script> | ||
</head> | ||
<body> | ||
<cast-media-player></cast-media-player> | ||
<script> | ||
const castContext = cast.framework.CastReceiverContext.getInstance(); | ||
const playerManager = castContext.getPlayerManager(); | ||
// Allowing storage is necessary to enable personalized ads. | ||
const consentSettings = new goog.cast.pal.ConsentSettings(); | ||
consentSettings.allowStorage = true; | ||
const nonceLoader = new goog.cast.pal.NonceLoader(consentSettings); | ||
// The existence of a nonce manager will prove that pal is being used | ||
// with the current media object. | ||
let nonceManager = null; | ||
|
||
// Flag used to identify first instance of playback in session. | ||
let playbackDidStart = false; | ||
|
||
// Configures and requests the NonceManager. | ||
// @return Promise promise that resolves when nonceManager is returned | ||
const requestNonceManager = () => { | ||
const request = new goog.cast.pal.NonceRequest(); | ||
request.adWillAutoPlay = true; | ||
request.adWillPlayMuted = true; | ||
request.continuousPlayback = false; | ||
request.descriptionUrl = 'https://example.com'; | ||
request.iconsSupported = true; | ||
request.playerType = 'Sample Player Type'; | ||
request.playerVersion = '1.0'; | ||
request.ppid = 'Sample PPID'; | ||
request.sessionId = 'Sample SID'; | ||
request.url = 'https://developers.google.com/ad-manager/pal/html5'; | ||
request.videoHeight = 480; | ||
request.videoWidth = 640; | ||
// We are returning a promise. | ||
return nonceLoader.loadNonceManager(request); | ||
}; | ||
|
||
// We'll need to attach a nonce to MediaInfo objects, when they are loaded. | ||
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.LOAD, (request) => { | ||
if (!request?.media?.vmapAdsRequest?.adTagUrl) { | ||
// We are only requesting nonces for media with an attached VMAP. | ||
return; | ||
} | ||
return requestNonceManager() | ||
.then((nm) => { | ||
nonceManager = nm; | ||
// We are generating a nonce and adding it to the existing VMAP url as the | ||
// query parameter 'paln'. | ||
const nonce = nonceManager.getNonce(); | ||
let vmapUrl = new URL(request.media.vmapAdsRequest.adTagUrl); | ||
vmapUrl.searchParams.set('paln', nonce); | ||
request.media.vmapAdsRequest.adTagUrl = vmapUrl.toString(); | ||
|
||
return request; | ||
}) | ||
.catch((error) => { | ||
console.warn('Nonce request failed with error: '); | ||
console.error(error); | ||
|
||
// Rather than failing, we fall back to requesting ads without the PAL nonce. | ||
return request; | ||
}); | ||
}); | ||
|
||
// Register the start of playback. | ||
playerManager.addEventListener(cast.framework.events.EventType.PLAYING, () => { | ||
if (playbackDidStart) return; | ||
|
||
playbackDidStart = true; | ||
nonceManager?.sendPlaybackStart(); | ||
}); | ||
|
||
// Register any interactions with the player. | ||
const interactionEvents = [ | ||
cast.framework.events.EventType.REQUEST_SEEK, | ||
cast.framework.events.EventType.REQUEST_STOP, | ||
cast.framework.events.EventType.REQUEST_PAUSE, | ||
cast.framework.events.EventType.REQUEST_PLAY, | ||
cast.framework.events.EventType.REQUEST_SKIP_AD, | ||
cast.framework.events.EventType.REQUEST_PLAY_AGAIN, | ||
cast.framework.events.EventType.REQUEST_PLAYBACK_RATE_CHANGE, | ||
cast.framework.events.EventType.REQUEST_VOLUME_CHANGE, | ||
cast.framework.events.EventType.REQUEST_USER_ACTION, | ||
cast.framework.events.EventType.REQUEST_FOCUS_STATE, | ||
]; | ||
playerManager.addEventListener(interactionEvents, (event) => { | ||
nonceManager?.sendAdTouch(event); | ||
}); | ||
|
||
// Register the end of playback. | ||
playerManager.addEventListener(cast.framework.events.EventType.MEDIA_FINISHED, () => { | ||
playbackDidStart = false; | ||
nonceManager?.sendPlaybackEnd(); | ||
}); | ||
|
||
castContext.start(); | ||
</script> | ||
</body> | ||
</html> |