TLTR:
From this (https://mixer.com/api/v1/channels/44877492/recordings) api endpoint I receive this value:
id: 302772586 (for example)
but the value that I need is this one:
https://mixer.com/CalypsoVibes?vod=2ArYJv4YfEaJezssnJR15Q
Where CalypsoVibes is the name of the user (I put the id that I return from another endpoint (channels/) and the vod id is something different from an integer.
Long story (not short):
I'm building a section to display the past (n) streams with their link from a user using the Mixer api.
Let's start saying that I didn't even know the existence of that website, so it has been really challenging to understand what data I need.
Here's my script and then I will explain the functionality:
$(() => {
let mixerAPI = 'https://mixer.com/api/v1/';
let streamName = 'GrimlockePrime';
let apiCall = mixerAPI + "channels/" + streamName;
function makeAjaxCall(url, methodType, callback){
$.ajax({
type: 'GET',
url : url,
method : methodType,
dataType : "json",
success : callback,
error : function (reason, xhr){
console.log("error in processing your request",reason);
}
})
}
makeAjaxCall(apiCall, "GET", function(respJson){
console.log(respJson.id);
let streamscall = mixerAPI + "recordings/165804890";
console.log(streamscall);
})
});
From this point, I will make another ajax call, using the id of the user's channel and put it in the record endpoint, to obtain this structure:
https://mixer.com/api/v1/recordings/channelId
The idea is to collect the last (n) streams from a user.
So, starting from a user profile on mixer.com, for example https://mixer.com/CalypsoVibes, I want to collect the last (n) number of streams, which are at the bottom of that section and are called "Past streams".
All of the resources has this structure
https://mixer.com/CalypsoVibes?vod=um8Nx4dNnUqj8E0jcDXKEw
so I probably need the vod id (?vod=id).
Oddly enough, I wasn't able to find any reference of that in the mixer api documentation. So I'm not 100% that I'm looking for the right endpoint.
There are a couple of them that could make the job:
1.https://mixer.com/api/v1/channels/userId/recordings 2.https://mixer.com/api/v1/recordings/id
Both of them will return vod values, but with a caveat which is the fact that they won't return any id or any useful link:
https://vodcontent-6001.xboxlive.com/channel-46362-public/7200a34f-7eca-4f81-822f-677afd4a8d97/
that's just an example of a return, but the id at the end of the url won't match any link from the app.
Does anyone ever had this problem?
VoDs on Mixer have an id
, which is an integer, and they have a contentId
(introduced with Season 2 and the v2 API), which is a UUID (or GUID, if you prefer). UUIDs can also be represented as base64 strings and this is what the VoD ID in the link to a VoD on Mixer.com is using.
In your example 2ArYJv4YfEaJezssnJR15Q
is the same as 26d80ad8-18fe-467c-897b-3b2c9c9475e5
.
If you have this ID you can then request GET
/vods/{contentId}
on the v2 API, which contains the various formats in the contentLocators
object field in the JSON response. They're descriptive and include the complete URL to video and thumbnail content, but the VoD chat log is missing.
You can also use the older v1 API. Personally, that's what I'd recommend.
For any given channel, you would first make a request to GET
/channels/{channelIdOrToken}
to then grab the channel ID from the id
property of the JSON response. Then you make a request to GET
/channels/{channelId}/recordings
, passing a query string of where=state:eq:AVAILABLE&order=createdAt:desc
(pick only those which are available, not processing or already deleted, and order them most recent to oldest) to grab a list of VoD stream recordings. Append the Recording
s in the response to a list, then examine the X-Total-Count
HTTP response header, and if it is greater than 50, and you have gotten a response of 50 items, add a page
query string parameter with a page counter and increment by one (from a default of 0
) and repeat the process until you have gotten them all.
You can then take your list of recordings and examine the vods
object field on each of them. There should be 4 items, each with a format
field of hls
, thumbnail
, raw
, or chat
.
You would grab the baseUrl
of each, and append the following filenames to get their corresponding files:
hls
: source.m3u8
(contains further references to actual transport stream files which are also found at this baseUrl
, but you probably don't want to deal with that)thumbnail
: source.png
, if the data.Has720pPreview
property is true
, also 720p.jpg
raw
: source.mp4
(this is the file that is offered to you on the website as a download option on your own channel, or on all channels for users with the Staff
, Founder
, or Guardian
roles)chat
: source.json
(this is a timestamped chat log, it includes all messages that have not been deleted, as well as user join and leave events)You can find an example making use of the v1 API to embed the 50 newest VoDs from LevelUpCast into jPlayer here.
$(document).ready(function ()
{
$form = $('#vod-form');
$form.on('submit', function (e)
{
e.preventDefault();
var channel = $form.find('input[name="channel"]').val();
var beamApiBase = 'https://mixer.com/api/v1';
var channelToken;
var channelId;
var vodListArray = [];
function getVod(page)
{
$.ajax({
url: beamApiBase + '/channels/' + channelId + '/recordings?where=state:eq:AVAILABLE&order=createdAt:desc&page=' + page,
success: function (data, textStatus, xhr)
{
for (var i = 0; i < data.length; i++)
{
if (data[i].state != 'AVAILABLE') // API is silly sometimes and ignores the query... 🤷🏻♀️
{
continue;
}
var thatVod = data[i];
var vodPaths = {
stream: null,
thumbnail: null,
small_thumbnail: null,
snapshot: null,
download: null,
chatlog: null
};
for (var j = 0; j < thatVod.vods.length; j++)
{
var thisVod = thatVod.vods[j];
switch (thisVod.format)
{
case 'hls':
{
vodPaths.stream = thisVod.baseUrl + 'manifest.m3u8';
break;
}
case 'thumbnail':
{
vodPaths.thumbnail = thisVod.baseUrl + 'source.png';
if (thisVod.data.Has720pPreview)
{
vodPaths.snapshot = thisVod.baseUrl + '720.jpg';
vodPaths.small_thumbnail = thisVod.baseUrl + '168p.png';
}
break;
}
case 'raw':
{
vodPaths.download = thisVod.baseUrl + 'source.mp4';
break;
}
case 'chat':
{
vodPaths.chatlog = thisVod.baseUrl + 'source.json';
break;
}
default:
{
console.warn('Unknown VoD format', thisVod);
break;
}
}
}
data[i].vodPaths = vodPaths;
var thisVodItem = {
'title': thatVod.name,
'artist': channelToken,
'date': new Date(Date.parse(thatVod.createdAt)),
'duration': thatVod.duration + 's',
'urls': vodPaths
};
vodListArray.push(thisVodItem);
}
if (data.length == 50 && xhr.getResponseHeader("X-Total-Count")*1 > 50)
{
getVod(page + 1);
}
else
{
if (vodListArray.length > 0)
{
var $vodList = $('#vod-list');
var $table = $('<table>');
var $tableTitleRow = $('<tr>');
var $tableItemNumberColumn = $('<th>');
$tableItemNumberColumn.text('Item #');
$tableTitleRow.append($tableItemNumberColumn);
var $tableThumbnailColumn = $('<th>');
$tableThumbnailColumn.text('Small Thumbnail');
$tableTitleRow.append($tableThumbnailColumn);
var $tableTitleColumn = $('<th>');
$tableTitleColumn.text('Title/Name');
$tableTitleRow.append($tableTitleColumn);
var $tableDateColumn = $('<th>');
$tableDateColumn.text('Date');
$tableTitleRow.append($tableDateColumn);
var $tableLinksColumn = $('<th>');
$tableLinksColumn.text('Links');
$tableTitleRow.append($tableLinksColumn);
$table.append($tableTitleRow);
for (var i = 0; i < vodListArray.length; i++)
{
var $tableRow = $('<tr>');
var $tableColumnItemNumber = $('<td>');
$tableColumnItemNumber.text(i + 1);
$tableRow.append($tableColumnItemNumber);
var $tableColumnThumbnail = $('<td>');
var $thumbnail = $('<img>');
$thumbnail.attr('src', vodListArray[i].urls.small_thumbnail);
$tableColumnThumbnail.append($thumbnail);
$tableRow.append($tableColumnThumbnail);
var $tableColumnTitle = $('<td>');
$tableColumnTitle.text(vodListArray[i].title);
$tableRow.append($tableColumnTitle);
var $tableColumnDate = $('<td>');
$tableColumnDate.text(vodListArray[i].date.toLocaleDateString() + ' ' + vodListArray[i].date.toLocaleTimeString());
$tableRow.append($tableColumnDate);
var $tableColumnLinks = $('<td>');
var $streamLink = $('<a>');
$streamLink.text("HLS Stream");
$streamLink.attr('href', vodListArray[i].urls.stream);
var $videoLink = $('<a>');
$videoLink.text("Video Download");
$videoLink.attr('href', vodListArray[i].urls.download);
var $chatLogLink = $('<a>');
$chatLogLink.text("Chat Log");
$chatLogLink.attr('href', vodListArray[i].urls.chatlog);
var $thumbnailLink = $('<a>');
$thumbnailLink.text("Thumbnail");
$thumbnailLink.attr('href', vodListArray[i].urls.thumbnail);
$tableColumnLinks.append($streamLink)
$tableColumnLinks.append($('<br>'));
$tableColumnLinks.append($videoLink);
$tableColumnLinks.append($('<br>'));
$tableColumnLinks.append($chatLogLink);
$tableColumnLinks.append($('<br>'));
$tableColumnLinks.append($thumbnailLink);
$tableRow.append($tableColumnLinks);
$table.append($tableRow);
}
$vodList.empty();
$vodList.append($table);
}
}
}});
}
$.getJSON(beamApiBase + '/channels/' + channel, function (data)
{
channelToken = data.token;
channelId = data.id;
getVod(0);
});
});
});
body
{
font-family: sans-serif;
}
table, tr, th, td
{
border: 1px solid;
}
<script src="https://zeptojs.com/zepto.min.js"></script>
<form id="vod-form">
Channel Name or ID: <input type="text" name="channel" length="20"></input>
<input type="submit" value="Get list"></input>
</form>
<div id="vod-list">
<p>The list will appear here.</p>
</div>