6
6
from random import shuffle , randint
7
7
from botocore .vendored import requests
8
8
import json
9
+ import urllib
9
10
logging .getLogger ('googleapiclient.discovery_cache' ).setLevel (logging .ERROR )
10
11
DEVELOPER_KEY = environ ['DEVELOPER_KEY' ]
11
12
YOUTUBE_API_SERVICE_NAME = 'youtube'
@@ -97,22 +98,24 @@ def build_cardless_audio_speechlet_response(output, should_end_session, url, tok
97
98
}
98
99
99
100
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 = {
102
103
'directives' : [{
103
104
'type' : 'AudioPlayer.Play' ,
104
- 'playBehavior' : 'ENQUEUE' ,
105
+ 'playBehavior' : playBehavior ,
105
106
'audioItem' : {
106
107
'stream' : {
107
108
'token' : str (next_token ),
108
- 'expectedPreviousToken' : str (previous_token ),
109
109
'url' : url ,
110
110
'offsetInMilliseconds' : 0
111
111
}
112
112
}
113
113
}],
114
114
'shouldEndSession' : should_end_session
115
115
}
116
+ if playBehavior == 'ENQUEUE' :
117
+ to_return ['directives' ][0 ]['audioItem' ]['stream' ]['expectedPreviousToken' ] = str (previous_token )
118
+ return to_return
116
119
117
120
118
121
def build_cancel_speechlet_response (title , output , should_end_session ):
@@ -212,8 +215,8 @@ def on_intent(event):
212
215
return change_mode (event , 's' , 0 )
213
216
elif intent_name == "AMAZON.ResumeIntent" :
214
217
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 )
217
220
elif intent_name == "AMAZON.LoopOnIntent" :
218
221
return change_mode (event , 'l' , 1 )
219
222
elif intent_name == "AMAZON.LoopOffIntent" :
@@ -236,13 +239,13 @@ def handle_playback(event):
236
239
elif request ['type' ] == 'AudioPlayer.PlaybackNearlyFinished' :
237
240
return nearly_finished (event )
238
241
elif request ['type' ] == 'AudioPlayer.PlaybackFailed' :
239
- return failed ()
242
+ return failed (event )
240
243
241
244
# --------------- Functions that control the skill's behavior ------------------
242
245
243
246
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 .'
246
249
should_end_session = False
247
250
return build_response (build_cardless_speechlet_response (speech_output , reprompt_text , should_end_session ))
248
251
@@ -265,7 +268,7 @@ def video_search(query):
265
268
search_response = youtube .search ().list (
266
269
q = query ,
267
270
part = 'id,snippet' ,
268
- maxResults = 25 ,
271
+ maxResults = 50 ,
269
272
type = 'video'
270
273
).execute ()
271
274
videos = []
@@ -278,10 +281,11 @@ def playlist_search(query):
278
281
search_response = youtube .search ().list (
279
282
q = query ,
280
283
part = 'id,snippet' ,
281
- maxResults = 25 ,
284
+ maxResults = 1 ,
282
285
type = 'playlist'
283
286
).execute ()
284
287
playlist_id = search_response .get ('items' )[0 ]['id' ]['playlistId' ]
288
+ playlist_title = search_response .get ('items' )[0 ]['snippet' ]['title' ]
285
289
videos = []
286
290
data = {'nextPageToken' :'' }
287
291
while 'nextPageToken' in data and len (videos ) < 25 :
@@ -292,17 +296,18 @@ def playlist_search(query):
292
296
videos .append (item ['snippet' ]['resourceId' ]['videoId' ])
293
297
except :
294
298
pass
295
- return videos
299
+ return videos , playlist_title
296
300
297
301
def channel_search (query ):
298
302
youtube = build (YOUTUBE_API_SERVICE_NAME , YOUTUBE_API_VERSION , developerKey = DEVELOPER_KEY )
299
303
search_response = youtube .search ().list (
300
304
q = query ,
301
305
part = 'id,snippet' ,
302
- maxResults = 25 ,
306
+ maxResults = 1 ,
303
307
type = 'channel'
304
308
).execute ()
305
309
playlist_id = search_response .get ('items' )[0 ]['id' ]['channelId' ]
310
+ playlist_title = search_response .get ('items' )[0 ]['snippet' ]['title' ]
306
311
data = {'nextPageToken' :'' }
307
312
videos = []
308
313
while 'nextPageToken' in data and len (videos ) < 25 :
@@ -313,7 +318,7 @@ def channel_search(query):
313
318
videos .append (item ['id' ]['videoId' ])
314
319
except :
315
320
pass
316
- return videos
321
+ return videos , playlist_title
317
322
318
323
def get_url_and_title (id ):
319
324
print ('Getting url for https://www.youtube.com/watch?v=' + id )
@@ -324,17 +329,27 @@ def get_url_and_title(id):
324
329
return first_stream .url , yt .title
325
330
except :
326
331
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'
328
342
329
343
def search (intent , session , shuffle_mode = False ):
330
344
query = intent ['slots' ]['query' ]['value' ]
331
345
should_end_session = True
332
346
print ('Looking for: ' + query )
333
347
intent_name = intent ['name' ]
348
+ playlist_title = None
334
349
if intent_name == "PlaylistIntent" or intent_name == "ShufflePlaylistIntent" :
335
- videos = playlist_search (query )
350
+ videos , playlist_title = playlist_search (query )
336
351
elif intent_name == "ChannelIntent" or intent_name == "ShuffleChannelIntent" :
337
- videos = channel_search (query )
352
+ videos , playlist_title = channel_search (query )
338
353
else :
339
354
videos = video_search (query )
340
355
if shuffle_mode :
@@ -348,8 +363,11 @@ def search(intent, session, shuffle_mode=False):
348
363
if next_url is None :
349
364
playlist ['p' ] = i
350
365
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
353
371
card_title = "Youtube"
354
372
return build_response (build_audio_speechlet_response (card_title , speech_output , should_end_session , next_url , next_token ))
355
373
@@ -382,6 +400,8 @@ def skip_action(event, skip):
382
400
return build_response (build_cardless_audio_speechlet_response (speech_output , should_end_session , next_url , next_token ))
383
401
384
402
def resume (event , say_title = False ):
403
+ if 'token' not in event ['context' ]['AudioPlayer' ]:
404
+ return get_welcome_response ()
385
405
current_token = event ['context' ]['AudioPlayer' ]['token' ]
386
406
should_end_session = True
387
407
speech_output = "Resuming..."
@@ -395,9 +415,9 @@ def resume(event, say_title = False):
395
415
def change_mode (event , mode , value ):
396
416
current_token = event ['context' ]['AudioPlayer' ]['token' ]
397
417
should_end_session = True
398
- playlist = convert_token_to_playlist (current_token )
418
+ playlist = convert_token_to_dict (current_token )
399
419
playlist [mode ] = str (value )
400
- current_token = convert_playlist_to_token (playlist )
420
+ current_token = convert_dict_to_token (playlist )
401
421
speech_output = "OK"
402
422
offsetInMilliseconds = event ['context' ]['AudioPlayer' ]['offsetInMilliseconds' ]
403
423
next_url , next_token , title = get_next_url_and_token (current_token , 0 )
@@ -413,7 +433,20 @@ def start_over(event):
413
433
speech_output = "Playing " + title
414
434
return build_response (build_cardless_audio_speechlet_response (speech_output , should_end_session , next_url , next_token ))
415
435
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 ):
417
450
pi = token .split ('&' )
418
451
playlist = {}
419
452
for i in pi :
@@ -422,14 +455,14 @@ def convert_token_to_playlist(token):
422
455
playlist [key ]= val
423
456
return playlist
424
457
425
- def convert_playlist_to_token (playlist ):
458
+ def convert_dict_to_token (playlist ):
426
459
token = "&" .join (["=" .join ([key , str (val )]) for key , val in playlist .items ()])
427
460
return token
428
461
429
462
def get_next_url_and_token (current_token , skip ):
430
463
should_end_session = True
431
464
speech_output = ''
432
- playlist = convert_token_to_playlist (current_token )
465
+ playlist = convert_token_to_dict (current_token )
433
466
next_url = None
434
467
title = None
435
468
shuffle_mode = int (playlist ['s' ])
@@ -455,7 +488,7 @@ def get_next_url_and_token(current_token, skip):
455
488
if skip == 0 :
456
489
break
457
490
playlist ['p' ] = str (next_playing )
458
- next_token = convert_playlist_to_token (playlist )
491
+ next_token = convert_dict_to_token (playlist )
459
492
return next_url , next_token , title
460
493
461
494
def stopped (event ):
@@ -470,7 +503,16 @@ def finished(event):
470
503
print ('finished' )
471
504
token = event ['request' ]['token' ]
472
505
473
- def failed ():
506
+ def failed (event ):
474
507
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 ))
0 commit comments