Skip to content

Commit b92b2d5

Browse files
committed
Add live videos, NowPlayingIntent, and bug fixes
1 parent 3a9c015 commit b92b2d5

File tree

2 files changed

+70
-28
lines changed

2 files changed

+70
-28
lines changed

lambda_function.py

+70-28
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from random import shuffle, randint
77
from botocore.vendored import requests
88
import json
9+
import urllib
910
logging.getLogger('googleapiclient.discovery_cache').setLevel(logging.ERROR)
1011
DEVELOPER_KEY=environ['DEVELOPER_KEY']
1112
YOUTUBE_API_SERVICE_NAME = 'youtube'
@@ -97,22 +98,24 @@ def build_cardless_audio_speechlet_response(output, should_end_session, url, tok
9798
}
9899

99100

100-
def build_audio_enqueue_response(should_end_session, url, previous_token, next_token):
101-
return {
101+
def build_audio_enqueue_response(should_end_session, url, previous_token, next_token, playBehavior='ENQUEUE'):
102+
to_return = {
102103
'directives': [{
103104
'type': 'AudioPlayer.Play',
104-
'playBehavior': 'ENQUEUE',
105+
'playBehavior': playBehavior,
105106
'audioItem': {
106107
'stream': {
107108
'token': str(next_token),
108-
'expectedPreviousToken': str(previous_token),
109109
'url': url,
110110
'offsetInMilliseconds': 0
111111
}
112112
}
113113
}],
114114
'shouldEndSession': should_end_session
115115
}
116+
if playBehavior == 'ENQUEUE':
117+
to_return['directives'][0]['audioItem']['stream']['expectedPreviousToken'] = str(previous_token)
118+
return to_return
116119

117120

118121
def build_cancel_speechlet_response(title, output, should_end_session):
@@ -212,8 +215,8 @@ def on_intent(event):
212215
return change_mode(event, 's', 0)
213216
elif intent_name == "AMAZON.ResumeIntent":
214217
return resume(event)
215-
elif intent_name == "AMAZON.RepeatIntent":
216-
return illegal_action()
218+
elif intent_name == "AMAZON.RepeatIntent" or intent_name == "NowPlayingIntent":
219+
return say_video_title(event)
217220
elif intent_name == "AMAZON.LoopOnIntent":
218221
return change_mode(event, 'l', 1)
219222
elif intent_name == "AMAZON.LoopOffIntent":
@@ -236,13 +239,13 @@ def handle_playback(event):
236239
elif request['type'] == 'AudioPlayer.PlaybackNearlyFinished':
237240
return nearly_finished(event)
238241
elif request['type'] == 'AudioPlayer.PlaybackFailed':
239-
return failed()
242+
return failed(event)
240243

241244
# --------------- Functions that control the skill's behavior ------------------
242245

243246
def get_welcome_response():
244-
speech_output = 'Welcome to Youtube. What should I search for?'
245-
reprompt_text = 'For example say, play videos by Fall Out Boy.'
247+
speech_output = 'Welcome to Youtube. Say, for example, play videos by The Beatles.'
248+
reprompt_text = 'Or you can say, shuffle songs by Michael Jackson.'
246249
should_end_session = False
247250
return build_response(build_cardless_speechlet_response(speech_output, reprompt_text, should_end_session))
248251

@@ -265,7 +268,7 @@ def video_search(query):
265268
search_response = youtube.search().list(
266269
q=query,
267270
part='id,snippet',
268-
maxResults=25,
271+
maxResults=50,
269272
type='video'
270273
).execute()
271274
videos = []
@@ -278,10 +281,11 @@ def playlist_search(query):
278281
search_response = youtube.search().list(
279282
q=query,
280283
part='id,snippet',
281-
maxResults=25,
284+
maxResults=1,
282285
type='playlist'
283286
).execute()
284287
playlist_id = search_response.get('items')[0]['id']['playlistId']
288+
playlist_title = search_response.get('items')[0]['snippet']['title']
285289
videos = []
286290
data={'nextPageToken':''}
287291
while 'nextPageToken' in data and len(videos) < 25:
@@ -292,17 +296,18 @@ def playlist_search(query):
292296
videos.append(item['snippet']['resourceId']['videoId'])
293297
except:
294298
pass
295-
return videos
299+
return videos, playlist_title
296300

297301
def channel_search(query):
298302
youtube = build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=DEVELOPER_KEY)
299303
search_response = youtube.search().list(
300304
q=query,
301305
part='id,snippet',
302-
maxResults=25,
306+
maxResults=1,
303307
type='channel'
304308
).execute()
305309
playlist_id = search_response.get('items')[0]['id']['channelId']
310+
playlist_title = search_response.get('items')[0]['snippet']['title']
306311
data={'nextPageToken':''}
307312
videos = []
308313
while 'nextPageToken' in data and len(videos) < 25:
@@ -313,7 +318,7 @@ def channel_search(query):
313318
videos.append(item['id']['videoId'])
314319
except:
315320
pass
316-
return videos
321+
return videos, playlist_title
317322

318323
def get_url_and_title(id):
319324
print('Getting url for https://www.youtube.com/watch?v='+id)
@@ -324,17 +329,27 @@ def get_url_and_title(id):
324329
return first_stream.url, yt.title
325330
except:
326331
print('Unable to get URL for '+id)
327-
return None, None
332+
return get_live_video_url_and_title(id)
333+
334+
def get_live_video_url_and_title(id):
335+
print('Live video?')
336+
info_url = 'https://www.youtube.com/get_video_info?&video_id='+id
337+
r = requests.get(info_url)
338+
info = convert_token_to_dict(r.text)
339+
raw_url = info['hlsvp']
340+
url = urllib.unquote(raw_url)
341+
return url, 'live video'
328342

329343
def search(intent, session, shuffle_mode=False):
330344
query = intent['slots']['query']['value']
331345
should_end_session = True
332346
print('Looking for: ' + query)
333347
intent_name = intent['name']
348+
playlist_title = None
334349
if intent_name == "PlaylistIntent" or intent_name == "ShufflePlaylistIntent":
335-
videos = playlist_search(query)
350+
videos, playlist_title = playlist_search(query)
336351
elif intent_name == "ChannelIntent" or intent_name == "ShuffleChannelIntent":
337-
videos = channel_search(query)
352+
videos, playlist_title = channel_search(query)
338353
else:
339354
videos = video_search(query)
340355
if shuffle_mode:
@@ -348,8 +363,11 @@ def search(intent, session, shuffle_mode=False):
348363
if next_url is None:
349364
playlist['p'] = i
350365
next_url, title = get_url_and_title(id)
351-
next_token = "&".join(["=".join([key, str(val)]) for key, val in playlist.items()])
352-
speech_output = "Playing " + title
366+
next_token = convert_dict_to_token(playlist)
367+
if playlist_title is None:
368+
speech_output = "Playing " + title
369+
else:
370+
speech_output = "Playing " + playlist_title
353371
card_title = "Youtube"
354372
return build_response(build_audio_speechlet_response(card_title, speech_output, should_end_session, next_url, next_token))
355373

@@ -382,6 +400,8 @@ def skip_action(event, skip):
382400
return build_response(build_cardless_audio_speechlet_response(speech_output, should_end_session, next_url, next_token))
383401

384402
def resume(event, say_title = False):
403+
if 'token' not in event['context']['AudioPlayer']:
404+
return get_welcome_response()
385405
current_token = event['context']['AudioPlayer']['token']
386406
should_end_session = True
387407
speech_output = "Resuming..."
@@ -395,9 +415,9 @@ def resume(event, say_title = False):
395415
def change_mode(event, mode, value):
396416
current_token = event['context']['AudioPlayer']['token']
397417
should_end_session = True
398-
playlist = convert_token_to_playlist(current_token)
418+
playlist = convert_token_to_dict(current_token)
399419
playlist[mode] = str(value)
400-
current_token = convert_playlist_to_token(playlist)
420+
current_token = convert_dict_to_token(playlist)
401421
speech_output = "OK"
402422
offsetInMilliseconds = event['context']['AudioPlayer']['offsetInMilliseconds']
403423
next_url, next_token, title = get_next_url_and_token(current_token, 0)
@@ -413,7 +433,20 @@ def start_over(event):
413433
speech_output = "Playing " + title
414434
return build_response(build_cardless_audio_speechlet_response(speech_output, should_end_session, next_url, next_token))
415435

416-
def convert_token_to_playlist(token):
436+
def say_video_title(event):
437+
should_end_session = True
438+
if 'token' in event['context']['AudioPlayer']:
439+
current_token = event['context']['AudioPlayer']['token']
440+
next_url, next_token, title = get_next_url_and_token(current_token, 0)
441+
if title is None:
442+
speech_output = "I can't find out the name of the current video."
443+
else:
444+
speech_output = "Now playing "+title
445+
else:
446+
speech_output = "Nothing is currently playing."
447+
return build_response(build_short_speechlet_response(speech_output, should_end_session))
448+
449+
def convert_token_to_dict(token):
417450
pi=token.split('&')
418451
playlist={}
419452
for i in pi:
@@ -422,14 +455,14 @@ def convert_token_to_playlist(token):
422455
playlist[key]=val
423456
return playlist
424457

425-
def convert_playlist_to_token(playlist):
458+
def convert_dict_to_token(playlist):
426459
token = "&".join(["=".join([key, str(val)]) for key, val in playlist.items()])
427460
return token
428461

429462
def get_next_url_and_token(current_token, skip):
430463
should_end_session = True
431464
speech_output = ''
432-
playlist = convert_token_to_playlist(current_token)
465+
playlist = convert_token_to_dict(current_token)
433466
next_url = None
434467
title = None
435468
shuffle_mode = int(playlist['s'])
@@ -455,7 +488,7 @@ def get_next_url_and_token(current_token, skip):
455488
if skip == 0:
456489
break
457490
playlist['p'] = str(next_playing)
458-
next_token = convert_playlist_to_token(playlist)
491+
next_token = convert_dict_to_token(playlist)
459492
return next_url, next_token, title
460493

461494
def stopped(event):
@@ -470,7 +503,16 @@ def finished(event):
470503
print('finished')
471504
token = event['request']['token']
472505

473-
def failed():
506+
def failed(event):
474507
print("Playback failed")
475-
476-
508+
print(event)
509+
if 'error' in event['request']:
510+
print(event['request']['error'])
511+
should_end_session = True
512+
playBehavior = 'REPLACE_ALL'
513+
current_token = event['request']['token']
514+
skip = 1
515+
next_url, next_token, title = get_next_url_and_token(current_token, skip)
516+
if title is None:
517+
return do_nothing()
518+
return build_response(build_audio_enqueue_response(should_end_session, next_url, current_token, next_token, playBehavior))

lambda_function.zip

1.79 KB
Binary file not shown.

0 commit comments

Comments
 (0)