Skip to content

Commit f6c5e2d

Browse files
committed
Add Soundcloud service
1 parent b806d22 commit f6c5e2d

File tree

4 files changed

+310
-0
lines changed

4 files changed

+310
-0
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
include "shared.lua"
2+
3+
DEFINE_BASECLASS( "mp_service_browser" )
4+
5+
local JS_Pause = "if(window.MediaPlayer) MediaPlayer.pause();"
6+
local JS_Play = "if(window.MediaPlayer) MediaPlayer.play();"
7+
local JS_Volume = "if(window.MediaPlayer) MediaPlayer.setVolume(%s * 100);"
8+
local JS_Seek = [[
9+
if (window.MediaPlayer) {
10+
var seekTime = %s
11+
var curTime = MediaPlayer.currentTime
12+
13+
var diffTime = Math.abs(curTime - seekTime)
14+
if (diffTime > 5) {
15+
MediaPlayer.currentTime = seekTime
16+
}
17+
}
18+
]]
19+
20+
local EMBED_HTML = [[
21+
<!doctype html>
22+
<html>
23+
24+
<head>
25+
<script src="https://w.soundcloud.com/player/api.js"></script>
26+
</head>
27+
28+
<body>
29+
<script>
30+
(async () => {
31+
const audioTrack = "https://soundcloud.com/{@audioPath}"
32+
const shouldPlay = {@shouldPlay}
33+
34+
const response = await fetch(`https://soundcloud.com/oembed?format=json&url=${audioTrack}`)
35+
const json = await response.json()
36+
37+
if (!!json && !!json.html) {
38+
const container = document.createElement('div');
39+
container.innerHTML = json.html;
40+
41+
document.body.appendChild(container)
42+
document.body.style.overflow = 'hidden';
43+
44+
const frame = container.firstElementChild
45+
var player = SC.Widget(frame);
46+
player.bind(SC.Widget.Events.READY, function () {
47+
var totalDuration = 0
48+
var curVol = 0
49+
var curTime = 0
50+
51+
player.getDuration((duration) => {
52+
totalDuration = duration
53+
54+
if (shouldPlay) {
55+
frame.setAttribute("height", window.innerHeight)
56+
57+
setInterval(function () {
58+
player.getVolume((volume) => { curVol = volume });
59+
player.getPosition((currentTime) => { curTime = currentTime });
60+
}, 100);
61+
62+
{ // Native audio controll
63+
player.currentTime = 0;
64+
player.duration = 0;
65+
66+
Object.defineProperty(player, "currentTime", {
67+
get() {
68+
return curTime / 1000;
69+
},
70+
set(time) {
71+
player.seekTo(time * 1000);
72+
},
73+
});
74+
75+
Object.defineProperty(player, "duration", {
76+
get() {
77+
return totalDuration / 1000;
78+
},
79+
});
80+
81+
player.play()
82+
window.MediaPlayer = player
83+
84+
}
85+
} else {
86+
var metadata = {
87+
duration: Math.round(totalDuration / 1000),
88+
title: json.title
89+
}
90+
91+
console.log("METADATA:" + JSON.stringify(metadata))
92+
}
93+
});
94+
});
95+
}
96+
})()
97+
98+
</script>
99+
</body>
100+
101+
</html>
102+
]]
103+
104+
function SERVICE:OnBrowserReady( browser )
105+
106+
-- Resume paused player
107+
if self._YTPaused then
108+
self.Browser:RunJavascript( JS_Play )
109+
self._YTPaused = nil
110+
return
111+
end
112+
113+
BaseClass.OnBrowserReady( self, browser )
114+
115+
local html = EMBED_HTML
116+
html = html:Replace("{@audioPath}", self:GetSoundCloudTrackId())
117+
html = html:Replace("{@shouldPlay}", "true")
118+
119+
browser:SetHTML(html)
120+
121+
end
122+
123+
function SERVICE:Pause()
124+
BaseClass.Pause( self )
125+
126+
if IsValid(self.Browser) then
127+
self.Browser:RunJavascript(JS_Pause)
128+
self._YTPaused = true
129+
end
130+
131+
end
132+
133+
function SERVICE:SetVolume( volume )
134+
local js = JS_Volume:format( MediaPlayer.Volume() )
135+
self.Browser:RunJavascript(js)
136+
end
137+
138+
function SERVICE:Sync()
139+
140+
local seekTime = self:CurrentTime()
141+
if IsValid(self.Browser) and self:IsTimed() and seekTime > 0 then
142+
self.Browser:RunJavascript(JS_Seek:format(seekTime))
143+
end
144+
end
145+
146+
function SERVICE:IsMouseInputEnabled()
147+
return IsValid( self.Browser )
148+
end
149+
150+
do -- Metadata Prefech
151+
function SERVICE:PreRequest( callback )
152+
153+
local trackid = self:GetSoundCloudTrackId()
154+
155+
local panel = vgui.Create("DHTML")
156+
panel:SetSize(500,500)
157+
panel:SetAlpha(0)
158+
panel:SetMouseInputEnabled(false)
159+
160+
svc = self
161+
function panel:ConsoleMessage(msg)
162+
163+
if msg:StartWith("METADATA:") then
164+
local metadata = util.JSONToTable(string.sub(msg, 10))
165+
166+
svc._metaTitle = metadata.title
167+
svc._metaDuration = metadata.duration
168+
callback()
169+
panel:Remove()
170+
end
171+
172+
if msg:StartWith("ERROR:") then
173+
local errmsg = string.sub(msg, 7)
174+
175+
callback(("SoundCloud Error: %s"):format(errmsg))
176+
panel:Remove()
177+
end
178+
end
179+
180+
local html = EMBED_HTML
181+
html = html:Replace("{@audioPath}", trackid)
182+
html = html:Replace("{@shouldPlay}", "false")
183+
184+
panel:SetHTML(html)
185+
186+
timer.Simple(10, function()
187+
if IsValid(panel) then
188+
panel:Remove()
189+
end
190+
end )
191+
end
192+
193+
function SERVICE:NetWriteRequest()
194+
net.WriteString( self._metaTitle )
195+
net.WriteUInt( self._metaDuration, 16 )
196+
end
197+
end
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
AddCSLuaFile "shared.lua"
2+
include "shared.lua"
3+
4+
function SERVICE:GetMetadata( callback )
5+
if self._metadata then
6+
callback( self._metadata )
7+
return
8+
end
9+
10+
local cache = MediaPlayer.Metadata:Query(self)
11+
12+
if MediaPlayer.DEBUG then
13+
print("MediaPlayer.GetMetadata Cache results:")
14+
PrintTable(cache or {})
15+
end
16+
17+
if cache then
18+
19+
local metadata = {}
20+
metadata.title = cache.title
21+
metadata.duration = tonumber(cache.duration)
22+
23+
self:SetMetadata(metadata)
24+
MediaPlayer.Metadata:Save(self)
25+
26+
callback(self._metadata)
27+
28+
else
29+
30+
local metadata = {}
31+
32+
-- Title & Duration is taken from Client via PreRequest
33+
metadata.title = self._metaTitle
34+
metadata.duration = self._metaDuration
35+
36+
self:SetMetadata(metadata, true)
37+
MediaPlayer.Metadata:Save(self)
38+
39+
callback(self._metadata)
40+
end
41+
end
42+
43+
function SERVICE:NetReadRequest()
44+
45+
if not self.PrefetchMetadata then return end
46+
47+
self._metaTitle = net.ReadString()
48+
self._metaDuration = net.ReadUInt( 16 )
49+
50+
end
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
DEFINE_BASECLASS( "mp_service_base" )
2+
3+
local urllib = url
4+
5+
SERVICE.Name = "SoundCloud"
6+
SERVICE.Id = "sc"
7+
SERVICE.Base = "browser"
8+
9+
SERVICE.PrefetchMetadata = true
10+
11+
local Ignored = {
12+
["sets"] = true,
13+
}
14+
15+
local function extractTrackId(urlinfo)
16+
if urlinfo.path then
17+
local path1, path2 = urlinfo.path:match("/([%a%d-_]+)/([%a%d-_]+)$")
18+
if path1 and not Ignored[path1] and path2 then
19+
return ("%s/%s"):format(path1, path2)
20+
end
21+
end
22+
23+
return false
24+
end
25+
26+
function SERVICE:New( url )
27+
local obj = BaseClass.New(self, url)
28+
obj._data = obj:GetSoundCloudTrackId()
29+
return obj
30+
end
31+
32+
function SERVICE:Match( url )
33+
if url:match("soundcloud.com") then
34+
local success, urlinfo = pcall(urllib.parse2, url)
35+
if not success then return false end
36+
37+
return extractTrackId(urlinfo)
38+
end
39+
40+
return false
41+
end
42+
43+
function SERVICE:GetSoundCloudTrackId()
44+
45+
local trackId
46+
47+
if self.trackId then
48+
49+
trackId = self.trackId
50+
51+
elseif self.urlinfo then
52+
53+
local id = extractTrackId(self.urlinfo)
54+
if id then
55+
trackId = id
56+
self.trackId = trackId
57+
end
58+
end
59+
60+
return trackId
61+
62+
end

workshop/lua/mediaplayer/sh_services.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ do
102102
-- Browser
103103
"browser", -- base
104104
"youtube",
105+
"soundcloud",
105106
"twitch",
106107
"dailymotion",
107108
"archive",

0 commit comments

Comments
 (0)