Do you own a huge MP3 collection and you want to be able to listen it on your Alexa device? Are you an aspiring band and want to share your music with your fans for free? Or maybe you are just curious about how the Alexa Music Skill works.
In the following article I will try to explain how to build a simple music streaming service. You can find the complete source code here: https://github.com/andrei-ace/music-cloud
Your Amazon Alexa will be able to respond to commands like:
- “Alexa, play music on skill name.”
- “Alexa, play songs by artist name on skill name.”
- “Alexa, play the song name on skill name.”
The songs are from your own private collection, no need to buy them again or pay a subscription. Sure, you can connect and stream from your phone like with any other bluetooth speakers, but where is the fun in that? You will miss all the vocal commands you can use with Alexa Music Skill.
Prerequisites
- An Amazon developer account. Sign up is free.
- An AWS account. You host your skill code as an AWS Lambda function. Make sure this is an US account.
- A Dropbox Basic (free) account
Step 1 — install all the tools you need
- Install npm
- Install awscli
- Install ask cli and configure AWS credentials
- Generate an access token for your Dropbox account
Step 2 — DynamoDB
Go to AWS Console and create a new table named cloud-music with primary key id of type String and a secondary index named artist_id-id-index with primary key artist_id and a Sort key named id:
Provision read and write capacity to not exceed 5 read and 5 write units. This will keep you in the AWS free tier.
Go to IAM and add “dynamodb:*” permissions for the ask user.
Step 3 — upload your MP3 files to Dropbox and create your music catalog
$ git clone https://github.com/andrei-ace/music-cloud
$ cd music-cloud/dropbox-catalog/
$ npm install#edit .env.template with your Dropbox access token$ node index upload -d ./mp3/
$ node index.js catalog
You should see the list of your MP3 files:
The first command, for each MP3 file from the ./mp3/ directory it will:
- read the ID3 tags — please make sure each file has at least the artist and the title
- upload the file to Dropbox
- create a shareable link
- save the ID3 tags and the shareable link to the cloud-music table
The second command will create two files: artists.json and songs.json. These two files represent the catalogs which will be uploaded to Alexa and make it understand your music collection.
Step 4 — create your Alexa Skill
Go to Alexa Developer Console and create a new skill named “Music Cloud”. Choose the Music model and press Create skill button. Go to Build tab and press the Endpoint menu (left). Copy the skill id.
Step 5 — create your Lambda function
Go to AWS Lambda functions and create a new function named MusicCloudLambda as described here. Make sure your execution role contains permissions for accessing DynamoDB:
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query"
],
"Resource": "*"
It should look like this:
Copy the Lambda’s ARN (from the upper right corner: ARN — arn:aws:lambda:us-east-1:*:function:MusicCloudLambda) and go back to your Alexa Skill -> Build -> Endpoint and paste it into the Default endpoint field. Save.
Step 6 — deploy the skill code
Edit the .ask/config.template file with your skill id and lambda ARN. Rename the file to .ask/config. Deploy:
$ ask-cli deploy
Step 7 — upload the catalog files to Alexa
First, let’s create the artists catalog:
$ ask api create-catalog --catalog-type AMAZON.MusicGroup --catalog-title catalog-artists --catalog-usage AlexaMusic.Catalog.MusicGroup
Save the catalog id and associate it to your Alexa Skill.
$ ask api associate-catalog-with-skill -s YOUR-SKILL-ID-HERE -c YOUR-ARTISTS-CATALOG-ID-HERE
Replace the YOUR-SKILL-ID-HERE and YOUR-ARTISTS-CATALOG-ID-HERE with your skill and artist catalog id values.
Now let’s upload the artists.json file:
$ ask api upload-catalog -c YOUR-ARTISTS-CATALOG-ID-HERE -f ./artists.json
Save the upload id, you’ll need it later to check the status of the upload.
Build better voice apps. Get more articles & interviews from voice technology experts at voicetechpodcast.com
Now do the same for the song catalog:
$ ask api create-catalog --catalog-type AMAZON.MusicRecording --catalog-title catalog-songs --catalog-usage AlexaMusic.Catalog.MusicRecording$ ask api associate-catalog-with-skill -s YOUR-SKILL-ID-HERE -c YOUR-SONGS-CATALOG-ID-HERE$ ask api upload-catalog -c YOUR-SONGS-CATALOG-ID-HERE -f ./songs.json
Save the upload id, you’ll need it later.
Step 8 — wait for the catalogs to be processed
Use the upload ids for both artist and song catalog to query for the upload status.
$ ask api get-catalog-upload -c YOUR-SONGS-CATALOG-ID-HERE -u YOUR-SONGS-UPLOAD-ID-HERE$ ask api get-catalog-upload -c YOUR-ARTISTS-CATALOG-ID-HERE -u YOUR-ARTISTS-UPLOAD-ID-HERE
Only after the ER_INGESTION step succeeds you can issue commands in the Test Console.
You can find more about catalogs here.
Step 9— test your Alexa Skill
Go to Alexa Developer Console -> Music Cloud skill -> Test tab and issue some test commands. If everything worked you should see something like this:
Don’t worry if you see the warning: “AudioPlayer is currently an unsupported namespace.” in the test console, that is normal. You will be able to listen to your songs on a real device like an Amazon Echo (or similar) that is linked with your developer account.
Catalog
There are 6 types of catalogs:
In our example we created catalogs just for the first two types — artists and songs.
An artist catalog looks like:
{...
"entities":[
{
"id":"g5NYvQMbvrqv76x+ItEErv5MJpE=",
"names":[
{
"language":"en",
"value":"SemMueL"
}
],
"popularity":{
"default":100
},
"lastUpdatedTime":"2019-10-08T14:51:23.326Z",
"deleted":false
}
]
}
A song catalog looks like:
{...
"entities":[
{
"id":"wuoQvgudnob9GJeKtqQxrAovVtY=",
"names":[
{
"language":"en",
"value":"Back in Time (Feat: Dylan Anthony Clark, Breanne Ponack)"
}
],
"popularity":{
"default":100
},
"lastUpdatedTime":"2019-10-08T14:51:18.849Z",
"artists":[
{
"id":"g5NYvQMbvrqv76x+ItEErv5MJpE=",
"names":[
{
"language":"en",
"value":"SemMueL"
}
]
}
],
"albums":[],
"deleted":false
}
]
}
Alexa Music Skill API
There are 4 types of requests that are mandatory for any music skill:
- GetPlayableContent — this request is sent when a user requests content to be played from your skill. If a user says: “Alexa, play It’s my life by Bon Jovi on music skill name” your skill will need to return the ContentId of that track as listed in your Amazon.MusicRecording catalog. If no entry can be found, an error message needs to be returned.
- Initiate — is sent when Alexa is ready for immediate playback of content obtained using a GetPlayableContent request. Alexa will send the ContentId and the skill responds with the stream URI.
- GetNextItem — is sent when a content queue was created, playback has started and Alexa needs the next item to buffer/enqueue for smooth song transition or if the user skipped the current item.
- GetPreviousItem — is sent when the content is playing and the user requests for the previous item.
GetPlayableContent
The skill needs to respond to three types of requests:
- “Alexa, play music on Music Cloud”
- “Alexa, play songs by artist name on Music Cloud”
- “Alexa, play song name on Music Cloud”
For the first type of request the selection criteria will look like:
"selectionCriteria": {
"attributes": [
{
"type": "MEDIA_TYPE",
"value": "TRACK"
}
]
}
The Music Cloud skill will return a random item from the DynamoDB table.
For the second type the selection criteria will look like:
"selectionCriteria": {
"attributes": [
{
"type": "ARTIST",
"entityId": "A2"
},
{
"type": "MEDIA_TYPE",
"value": "TRACK"
}
]
}
The Music Cloud skill will return a random song by the requested artist.
And lastly:
"selectionCriteria": {
"attributes": [
{
"type": "TRACK",
"entityId": "138545995"
},
{
"type": "MEDIA_TYPE",
"value": "TRACK"
}
]
}
The skill will return the exact song with the ContendId equal with the requested entityId.
Initiate
The important part from the Initiate request is:
"payload": {
"filters": {
"explicitLanguageAllowed": true
},
"contentId": "1021012f-12bb-4938-9723-067a4338b6d0",
"playbackModes": {
"shuffle": false,
"loop": false
}
}
The skill will need to respond with a playable stream:
"playbackMethod": {
"type": "ALEXA_AUDIO_PLAYER_QUEUE",
"id": "Queue-Id",
"rules": {
"feedback": {
"type": "PREFERENCE",
"enabled": true
}
},
"firstItem": {
"id": "1021012f-12bb-4938-9723-067a4338b6d0",
"playbackInfo": {
"type": "DEFAULT"
},
"metadata": {
"type": "TRACK",
"name": {
"speech": {
"type": "PLAIN_TEXT",
"text": "jeremy"
},
"display": "Jeremy"
},
"art": {}
},
"controls": [
{
"type": "COMMAND",
"name": "NEXT",
"enabled": true
}
],
"rules": {
"feedbackEnabled": true
},
"stream": {
"id": "STREAMID_92_14629004",
"uri": "https://cdn.example.com/api/1/a2f318467fbf2829996adc0880e0abd03d03b1ba6ac.mp3",
"offsetInMilliseconds": 0,
"validUntil": "2020-05-10T19:11:35Z"
},
"feedback": {
"type": "PREFERENCE",
"value": "POSITIVE"
}
}
}
GetNextItem
This will return a random song. The response data is similar with the Initiate response.
GetPreviousItem
This will return an error as the queue is not persisted by the skill at this point.
Some ideas for improvement:
- Add other types of catalogs (genre, playlists like: top 100 songs this week, etc)
- Improve the voice modelling by adding more and better data in the catalogs. An example would be to provide alternative names for artists, spell out numbers and non-alphabetic characters
- Track user playlists and song popularity
- Create a music recommendation system (maybe using TensorFlow)