├── .gitattributes ├── pawn.json ├── .gitignore ├── README.md ├── test.pwn ├── example.pwn └── MV_Youtube.inc /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pwn linguist-language=Pawn 2 | *.inc linguist-language=Pawn 3 | -------------------------------------------------------------------------------- /pawn.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "MichaelBelgium", 3 | "repo": "MV_Youtube", 4 | "entry": "test.pwn", 5 | "output": "test.amx", 6 | "dependencies": [ 7 | "Southclaws/pawn-requests" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Package only files 3 | # 4 | 5 | # Compiled Bytecode, precompiled output and assembly 6 | *.amx 7 | *.lst 8 | *.asm 9 | 10 | # Vendor directory for dependencies 11 | dependencies/ 12 | 13 | # Dependency versions lockfile 14 | pawn.lock 15 | 16 | 17 | # 18 | # Server/gamemode related files 19 | # 20 | 21 | # compiled settings file 22 | # keep `samp.json` file on version control 23 | # but make sure the `rcon_password` field is set externally 24 | # you can use the environment variable `SAMP_RCON_PASSWORD` to do this. 25 | server.cfg 26 | 27 | # Plugins directory 28 | plugins/ 29 | 30 | # binaries 31 | *.exe 32 | *.dll 33 | *.so 34 | announce 35 | samp03svr 36 | samp-npc 37 | 38 | # logs 39 | logs/ 40 | server_log.txt 41 | 42 | # 43 | # Common files 44 | # 45 | 46 | *.sublime-workspace 47 | *.sublime-project 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Youtube 2 | 3 | [MV]_Youtube, with this include players are able to stream youtube video's in-game. 4 | 5 | ## Functions and callbacks: 6 | 7 | 8 | ```PAWN 9 | native PlayYoutubeVideoFor(const url[], playfor = INVALID_PLAYER_ID, playlist = INVALID_PLAYLIST_ID, bool:usepos = false, Float:distance = 50.0) 10 | -> returns a youtubeid 11 | native StopYoutubeVideo(youtubeid) 12 | 13 | native IsValidYoutubeURL(const string[]) 14 | native IsYouTubeVideoPlaying(youtubeid) 15 | 16 | native GetVideoDuration(youtubeid) 17 | native GetVideoTitle(youtubeid) 18 | native GetVideoLink(youtubeid) 19 | native GetVideoStreamLink(youtubeid) 20 | native GetVideoTarget(youtubeid) 21 | 22 | native CreatePlaylist(const name[]) 23 | -> returns a playlistid 24 | native RemovePlaylist(playlistid) 25 | native GetPlaylistName(playlistid) 26 | native GetPlaylistSongs(playlistid) 27 | native GetPlaylistSongsCount(playlistid) 28 | native GetPlaylistFromVideo(youtubeid) 29 | 30 | native SearchYoutubeVideos(playerid, searchquery[]) 31 | public OnYoutubeSearch(playerid) 32 | 33 | public OnYoutubeVideoStart(youtubeid) 34 | public OnYoutubeVideoFinished(youtubeid) 35 | public OnMVYoutubeError(youtubeid, const message[]) 36 | public OnPlaylistAddEntry(playlistid, youtubeid) 37 | public OnPlaylistFinished(playlistid) 38 | ``` 39 | 40 | # Requirements 41 | 42 | * [pawn-requests](https://github.com/Southclaws/pawn-requests) 43 | * [Hash-map implementation in PAWN](https://github.com/BigETI/pawn-map) (optional) 44 | 45 | # Installing 46 | 47 | Using sampctl: 48 | 49 | `sampctl package install MichaelBelgium/MV_Youtube` 50 | 51 | And/or manually: 52 | 53 | ```pawn 54 | #include 55 | ``` 56 | 57 | ## Youtube API 58 | 59 | This include requires a Youtube API.. which one? This one: [Youtube-to-mp3-API](https://github.com/MichaelBelgium/Youtube-to-mp3-API) 60 | 61 | You are free to use the existing settings of this include that uses the installed youtube API on my domain. If preferred, you can install the API yourself on your own dedicated server/vps and configure the include to use that. 62 | 63 | ## Settings 64 | 65 | ```PAWN 66 | #define MAX_YOUTUBE_SAVES 50 67 | #define MAX_PLAYLISTS 5 68 | 69 | #define CONVERTER_PATH "https://youtube.michaelbelgium.me/api/converter/" 70 | #define MAX_SEARCH_RESULTS 5 71 | ``` 72 | 73 | # Images 74 | ![Song info](http://puu.sh/oRnMo.jpg) 75 | ![Song info](http://puu.sh/oRnNh.png) 76 | -------------------------------------------------------------------------------- /test.pwn: -------------------------------------------------------------------------------- 1 | // generated by "sampctl package generate" 2 | 3 | #include "MV_Youtube.inc" 4 | 5 | new youtube_ids[3], myPlaylist; 6 | 7 | main() { 8 | for (new i = 0; i < sizeof(youtube_ids); i++) 9 | youtube_ids[i] = INVALID_YT_ID; 10 | 11 | youtube_ids[0] = PlayYoutubeVideoFor("https://www.youtube.com/watch?v=ehCQMDKVJqU"); 12 | SetTimer("OnTest", 10000, false); 13 | 14 | SearchYoutubeVideos(0, "michaelbelgium"); 15 | } 16 | 17 | forward OnTest(); 18 | public OnTest() 19 | { 20 | printSong(0); 21 | 22 | myPlaylist = CreatePlaylist("Playlist 1"); 23 | youtube_ids[1] = PlayYoutubeVideoFor("https://www.youtube.com/watch?v=jfreFPe99GU", INVALID_PLAYER_ID, myPlaylist); 24 | youtube_ids[2] = PlayYoutubeVideoFor("https://www.youtube.com/watch?v=NkrkAsRVLEA", INVALID_PLAYER_ID, myPlaylist); 25 | SetTimer("OnTestPlaylist", 10000, false); 26 | } 27 | 28 | forward OnTestPlaylist(); 29 | public OnTestPlaylist() 30 | { 31 | printSong(1); 32 | printSong(2); 33 | 34 | print("---------- Testing playlist response ----------"); 35 | printf("Id: %i", myPlaylist); 36 | printf("Name: %s", GetPlaylistName(myPlaylist)); 37 | printf("Songcount: %i", GetPlaylistSongsCount(myPlaylist)); 38 | } 39 | 40 | stock printSong(index) 41 | { 42 | printf("---------- Testing video response %i ----------", youtube_ids[index]); 43 | printf("Title: %s", GetVideoTitle(youtube_ids[index])); 44 | printf("Link: %s", GetVideoLink(youtube_ids[index])); 45 | printf("Duration: %i seconds", GetVideoDuration(youtube_ids[index])); 46 | printf("Stream: %s", GetVideoStreamLink(youtube_ids[index])); 47 | printf("Play for: %i", GetVideoTarget(youtube_ids[index])); 48 | printf("Playlist id: %i", GetPlaylistFromVideo(youtube_ids[index])); 49 | } 50 | 51 | public OnMVYoutubeError(youtubeid, const message[]) 52 | { 53 | printf("Video %i encountered an error: %s", youtubeid, message); 54 | } 55 | 56 | public OnPlaylistAddEntry(playerlistid, youtubeid) 57 | { 58 | printf("Added video %i to playlist %i", youtubeid, playerlistid); 59 | } 60 | 61 | public OnYoutubeVideoStart(youtubeid) 62 | { 63 | printf("Video %i started", youtubeid); 64 | } 65 | 66 | public OnPlaylistFinished(playlistid) 67 | { 68 | printf("Playlist %i finished", playlistid); 69 | } 70 | 71 | public OnYoutubeSearch(playerid) 72 | { 73 | print("---------- Search response ----------"); 74 | 75 | for(new i = 0; i < MAX_SEARCH_RESULTS; i++) 76 | { 77 | printf("Title: %s", SearchResults[playerid][i][Title]); 78 | printf("Link: %s", SearchResults[playerid][i][Link]); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example.pwn: -------------------------------------------------------------------------------- 1 | #define FILTERSCRIPT 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define COLOR_RED 0xAA3333AA 9 | #define DIALOG_SEARCH_AND_PLAY 555 10 | 11 | new gYoutubeID[MAX_PLAYERS], gYoutubeIDForAll; 12 | new gMyPlaylist[MAX_PLAYERS], gEveryonesPlaylist, gTotalPlaylists; 13 | 14 | public OnFilterScriptInit() 15 | { 16 | print("\n--------------------------------------"); 17 | print(" Example filterscript to use with [MV]_Youtube"); 18 | print("--------------------------------------\n"); 19 | 20 | for(new i = 0; i < MAX_PLAYERS; i++) gYoutubeID[i] = INVALID_YT_ID; 21 | gYoutubeIDForAll = INVALID_YT_ID; 22 | 23 | gEveryonesPlaylist = CreatePlaylist("For All"); 24 | gTotalPlaylists = 1; 25 | return 1; 26 | } 27 | 28 | public OnFilterScriptExit() 29 | { 30 | RemovePlaylist(gEveryonesPlaylist); 31 | return 1; 32 | } 33 | 34 | public OnPlayerConnect(playerid) 35 | { 36 | gMyPlaylist[playerid] = INVALID_PLAYLIST_ID; 37 | return 1; 38 | } 39 | 40 | public OnPlayerDisconnect(playerid, reason) 41 | { 42 | if(gMyPlaylist[playerid] != INVALID_PLAYLIST_ID) 43 | { 44 | RemovePlaylist(gMyPlaylist[playerid]); 45 | gTotalPlaylists--; 46 | } 47 | return 1; 48 | } 49 | 50 | // ============ PLAYLISTS ==================== 51 | CMD:search(playerid, params[]) 52 | { 53 | //this example will search for video's, list them in a dialog and when clicking on one of them they will download/play. 54 | new mysearch[128]; 55 | if(sscanf(params, "s[128]", mysearch) || strlen(mysearch) > 64) return SendClientMessage(playerid, COLOR_RED, "Usage: /search [something]"); 56 | SearchYoutubeVideos(playerid, mysearch); 57 | return 1; 58 | } 59 | 60 | CMD:createmyplaylist(playerid,params[]) 61 | { 62 | new name[128], string[128]; 63 | if(gTotalPlaylists >= MAX_PLAYLISTS) return SendClientMessage(playerid, COLOR_RED, "Reached servers max playlists"); 64 | if(gMyPlaylist[playerid] != INVALID_PLAYLIST_ID) return SendClientMessage(playerid, COLOR_RED, "You already made a playlist"); 65 | if(sscanf(params, "s[128]", name) || strlen(name) > 32) return SendClientMessage(playerid, COLOR_RED, "Usage: /createmyplaylist [playlist name]"); 66 | gMyPlaylist[playerid] = CreatePlaylist(name); 67 | gTotalPlaylists++; 68 | 69 | format(string, sizeof(string), "You created your own playlist \"%s\". Use /addtomyplaylist to add youtube songs", GetPlaylistName(gMyPlaylist[playerid])); 70 | SendClientMessage(playerid, -1, string); 71 | return 1; 72 | } 73 | 74 | CMD:addtomyplaylist(playerid,params[]) 75 | { 76 | new song[256]; 77 | if(gMyPlaylist[playerid] == INVALID_PLAYLIST_ID) return SendClientMessage(playerid, COLOR_RED, "You don't have a playlist"); 78 | if(sscanf(params, "s[256]", song)) return SendClientMessage(playerid, COLOR_RED, "Usage: /addtomyplaylist [youtube url]"); 79 | if(!IsValidYoutubeURL(song)) return SendClientMessage(playerid, COLOR_RED, "Invalid url."); 80 | 81 | PlayYoutubeVideoFor(song, playerid, gMyPlaylist[playerid]); 82 | return 1; 83 | } 84 | 85 | CMD:addtoglobalplaylist(playerid,params[]) 86 | { 87 | new song[256]; 88 | if(sscanf(params, "s[256]", song)) return SendClientMessage(playerid, COLOR_RED, "Usage: /addtoglobalplaylist [youtube url]"); 89 | if(!IsValidYoutubeURL(song)) return SendClientMessage(playerid, COLOR_RED, "Invalid url."); 90 | 91 | PlayYoutubeVideoFor(song, playerid, gEveryonesPlaylist); 92 | return 1; 93 | } 94 | 95 | CMD:myplaylist(playerid,params[]) 96 | { 97 | if(gMyPlaylist[playerid] == INVALID_PLAYLIST_ID) return SendClientMessage(playerid, COLOR_RED, "You don't have a playlist"); 98 | 99 | new songs[MAX_YOUTUBE_SAVES], string[128], plist[256*5]; 100 | songs = GetPlaylistSongs(gMyPlaylist[playerid]); 101 | 102 | strcat(plist, "Entry\tSong\tDuration\n"); 103 | 104 | for(new i = 0; i < MAX_YOUTUBE_SAVES; i++) 105 | { 106 | if(songs[i] == INVALID_YT_ID) continue; 107 | format(string, sizeof(string), "%i\t%s\t%i seconds\n", i+1, GetVideoTitle(songs[i]), GetVideoDuration(songs[i])); 108 | strcat(plist, string); 109 | } 110 | 111 | ShowPlayerDialog(playerid, 0, DIALOG_STYLE_TABLIST_HEADERS, GetPlaylistName(gMyPlaylist[playerid]), plist, "OK", ""); 112 | return 1; 113 | } 114 | 115 | CMD:globalplaylist(playerid,params[]) 116 | { 117 | new songs[MAX_YOUTUBE_SAVES], string[128], plist[256*5]; 118 | songs = GetPlaylistSongs(gEveryonesPlaylist); 119 | 120 | strcat(plist, "Entry\tSong\tDuration\n"); 121 | 122 | for(new i = 0; i < MAX_YOUTUBE_SAVES; i++) 123 | { 124 | if(songs[i] == INVALID_YT_ID) continue; 125 | format(string, sizeof(string), "%i\t%s\t%i seconds\n", i+1, GetVideoTitle(songs[i]), GetVideoDuration(songs[i])); 126 | strcat(plist, string); 127 | } 128 | 129 | ShowPlayerDialog(playerid, 0, DIALOG_STYLE_TABLIST_HEADERS, GetPlaylistName(gEveryonesPlaylist), plist, "OK", ""); 130 | return 1; 131 | } 132 | 133 | // =========== NO PLAYLISTS =============== 134 | 135 | CMD:playforme(playerid,params[]) 136 | { 137 | new song[256]; 138 | if(sscanf(params, "s[256]", song)) return SendClientMessage(playerid, COLOR_RED, "Usage: /playforme [youtube url]"); 139 | if(!IsValidYoutubeURL(song)) return SendClientMessage(playerid, COLOR_RED, "Invalid url."); 140 | if(IsYouTubeVideoPlaying(gYoutubeID[playerid])) return SendClientMessage(playerid, COLOR_RED, "A song is already playing."); 141 | 142 | gYoutubeID[playerid] = PlayYoutubeVideoFor(song, playerid); 143 | return 1; 144 | } 145 | 146 | CMD:playforall(playerid,params[]) 147 | { 148 | new song[256]; 149 | if(sscanf(params, "s[256]", song)) return SendClientMessage(playerid, COLOR_RED, "Usage: /playforall [youtube url]"); 150 | if(!IsValidYoutubeURL(song)) return SendClientMessage(playerid, COLOR_RED, "Invalid url."); 151 | if(IsYouTubeVideoPlaying(gYoutubeIDForAll)) return SendClientMessage(playerid, COLOR_RED, "A song is already playing."); 152 | 153 | gYoutubeIDForAll = PlayYoutubeVideoFor(song); 154 | return 1; 155 | } 156 | 157 | CMD:whatsongisplayingforme(playerid,params[]) 158 | { 159 | new playing[256]; 160 | 161 | format(playing, sizeof(playing), "Name: %s\nDuration: %i seconds\nLink: %s", GetVideoTitle(gYoutubeID[playerid]), GetVideoDuration(gYoutubeID[playerid]), GetVideoLink(gYoutubeID[playerid])); 162 | ShowPlayerDialog(playerid, 0, DIALOG_STYLE_MSGBOX, "Now playing", playing, "OK", ""); 163 | return 1; 164 | } 165 | 166 | CMD:whatsongisplaying(playerid,params[]) 167 | { 168 | new playing[256]; 169 | 170 | format(playing, sizeof(playing), "Name: %s\nDuration: %i seconds\nLink: %s", GetVideoTitle(gYoutubeIDForAll), GetVideoDuration(gYoutubeIDForAll), GetVideoLink(gYoutubeIDForAll)); 171 | ShowPlayerDialog(playerid, 0, DIALOG_STYLE_MSGBOX, "Now playing", playing, "OK", ""); 172 | return 1; 173 | } 174 | 175 | // ======================================== 176 | 177 | public OnYoutubeVideoStart(youtubeid) 178 | { 179 | new string[256], time[3]; 180 | 181 | formatSeconds(GetVideoDuration(youtubeid),time[0],time[1],time[2]); 182 | format(string,sizeof(string),"{0049FF}[Now playing] {00c9ff}%s (Duration: %02dh %02dm %02ds)",GetVideoTitle(youtubeid),time[0],time[1],time[2]); 183 | 184 | if(GetPlaylistFromVideo(youtubeid) != INVALID_PLAYLIST_ID) 185 | format(string, sizeof(string), "%s (Playlist: %s (%d))",string, GetPlaylistName(GetPlaylistFromVideo(youtubeid)), GetPlaylistFromVideo(youtubeid)); 186 | 187 | if(GetVideoTarget(youtubeid) != INVALID_PLAYER_ID) 188 | SendClientMessage(GetVideoTarget(youtubeid), -1, string); 189 | else 190 | SendClientMessageToAll(-1, string); 191 | 192 | return 1; 193 | } 194 | 195 | public OnYoutubeVideoFinished(youtubeid) 196 | { 197 | new string[128]; 198 | if(youtubeid == gYoutubeIDForAll) 199 | { 200 | format(string, sizeof(string), "The song that played for everyone (%s) has finished. Execute /playforall to play another song.", GetVideoTitle(youtubeid)); 201 | SendClientMessageToAll(-1, string); 202 | } 203 | else 204 | { 205 | for(new i = 0; i < MAX_PLAYERS; i++) 206 | { 207 | if(gYoutubeID[i] == youtubeid) 208 | { 209 | format(string, sizeof(string), "The song that played for you (%s) has finished. Execute /playforme to play another song.", GetVideoTitle(youtubeid)); 210 | SendClientMessage(i, -1, string); 211 | break; 212 | } 213 | } 214 | } 215 | return 1; 216 | } 217 | 218 | public OnPlaylistFinished(playlistid) 219 | { 220 | new string[128]; 221 | if(playlistid == gYoutubeIDForAll) 222 | { 223 | format(string, sizeof(string), "{0049FF}[Playlist '%s'] {00c9ff}Finished. Type /addtoglobalplaylist to add songs to the playlist.",GetPlaylistName(playlistid)); 224 | SendClientMessageToAll(-1, string); 225 | } 226 | else 227 | { 228 | for(new i = 0; i < MAX_PLAYERS; i++) 229 | { 230 | if(!IsPlayerConnected(i)) continue; 231 | if(gMyPlaylist[i] == playlistid) 232 | { 233 | format(string, sizeof(string), "{0049FF}[Playlist '%s'] {00c9ff}Finished. Type /addtomyplaylist to add songs to the playlist.",GetPlaylistName(playlistid)); 234 | SendClientMessage(i, -1, string); 235 | break; 236 | } 237 | } 238 | } 239 | return 1; 240 | } 241 | 242 | public OnPlaylistAddEntry(playerlistid, youtubeid) 243 | { 244 | new string[128]; 245 | format(string, sizeof(string), "{0049FF}[Playlist '%s'] {00c9ff}Added song '%s' (%i) ",GetPlaylistName(playerlistid), GetVideoTitle(youtubeid), youtubeid); 246 | 247 | if(GetVideoTarget(youtubeid) == INVALID_PLAYER_ID) 248 | SendClientMessageToAll(-1, string); 249 | else 250 | SendClientMessage(GetVideoTarget(youtubeid) , -1, string); 251 | return 1; 252 | } 253 | 254 | public OnMVYoutubeError(youtubeid, const message[]) 255 | { 256 | new string[256], player = GetVideoTarget(youtubeid); 257 | 258 | format(string, sizeof(string), "An error has occured while downloading video %s: %s", GetVideoLink(youtubeid), message); 259 | 260 | if(player != INVALID_PLAYER_ID) 261 | SendClientMessage(player, COLOR_RED, string); 262 | else 263 | SendClientMessageToAll(COLOR_RED, string); 264 | return 1; 265 | } 266 | 267 | public OnYoutubeSearch(playerid) 268 | { 269 | /* 270 | To get the results of the player who searched: 271 | 272 | enum pSearchResult 273 | { 274 | Title[64], 275 | Link[128] 276 | }; 277 | 278 | new SearchResults[MAX_PLAYERS][MAX_SEARCH_RESULTS][pSearchResult]; 279 | */ 280 | 281 | new string[128],searchdialog[256*5]; 282 | 283 | for(new i = 0; i < MAX_SEARCH_RESULTS; i++) 284 | { 285 | // printf("%s", SearchResults[playerid][i][Title]); 286 | // printf("%s", SearchResults[playerid][i][Link]); 287 | 288 | format(string, sizeof(string), "%s\n", SearchResults[playerid][i][Title]); 289 | strcat(searchdialog, string); 290 | } 291 | 292 | ShowPlayerDialog(playerid, DIALOG_SEARCH_AND_PLAY, DIALOG_STYLE_LIST, "Results", searchdialog, "Play", "Exit"); 293 | return 1; 294 | } 295 | 296 | stock formatSeconds(seconds, &hours_left, &minutes_left, &seconds_left) 297 | { 298 | hours_left = seconds/60/60; 299 | minutes_left = (seconds - hours_left*60*60)/60; 300 | seconds_left = (seconds - hours_left*60*60 - minutes_left*60); 301 | } 302 | 303 | public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[]) 304 | { 305 | if(dialogid == DIALOG_SEARCH_AND_PLAY) 306 | { 307 | if(SearchResults[playerid][listitem][Link][0] == EOS) return 1; 308 | PlayYoutubeVideoFor(SearchResults[playerid][listitem][Link], playerid); 309 | return 1; 310 | } 311 | return 0; 312 | } 313 | -------------------------------------------------------------------------------- /MV_Youtube.inc: -------------------------------------------------------------------------------- 1 | #if defined MV_Youtube_Included 2 | #endinput 3 | #endif 4 | #define MV_Youtube_Included 5 | 6 | #include 7 | #include 8 | #tryinclude 9 | 10 | #if !defined MAX_YOUTUBE_SAVES 11 | #define MAX_YOUTUBE_SAVES 50 12 | #endif 13 | 14 | #if !defined CONVERTER_PATH 15 | #define CONVERTER_PATH "https://youtube.michaelbelgium.me/api/converter/" 16 | #endif 17 | 18 | #if !defined MAX_SEARCH_RESULTS 19 | #define MAX_SEARCH_RESULTS 5 20 | #endif 21 | 22 | #if !defined API_KEY 23 | #define API_KEY "" 24 | #endif 25 | 26 | #if !defined MAX_PLAYLISTS 27 | #define MAX_PLAYLISTS 5 28 | #endif 29 | 30 | #define INVALID_YT_ID -1 31 | #define INVALID_PLAYLIST_ID -1 32 | 33 | static RequestsClient:Converter; 34 | 35 | #if defined __MAP_INCLUDED__ 36 | static Map:LoadRequestToPlayerID; 37 | static Map:LoadRequestToYoutubeID; 38 | #else 39 | static Request:PlayerIDToRequest[MAX_PLAYERS] = {Request:-1, ...}; 40 | #endif 41 | 42 | enum e_ytv 43 | { 44 | #if !defined __MAP_INCLUDED__ 45 | Request:RequestID, 46 | #endif 47 | 48 | bool:Playing, 49 | ytID[16], 50 | Duration, 51 | Title[256], 52 | Link[256], 53 | StreamLink[256], 54 | PlayFor, 55 | PlaylistID, 56 | Float:Distance, 57 | bool:UsePos, 58 | Timer 59 | }; 60 | 61 | new Youtube[MAX_YOUTUBE_SAVES][e_ytv]; 62 | 63 | enum e_plist 64 | { 65 | bool:Active, 66 | Name[32] 67 | }; 68 | 69 | new Playlist[MAX_PLAYLISTS][e_plist]; 70 | 71 | enum pSearchResult 72 | { 73 | Title[128], 74 | Link[128] 75 | }; 76 | 77 | new SearchResults[MAX_PLAYERS][MAX_SEARCH_RESULTS][pSearchResult]; 78 | 79 | static stock EncodeYoutubeURL(url[], maxlength = sizeof(url)) 80 | { 81 | new const SEARCH[] = ":", REPLACEMENT[] = "%3A", SUB_LEN = strlen(SEARCH), REP_LEN = strlen(REPLACEMENT); 82 | new pos, len = strlen(url), count; 83 | 84 | while(-1 != (pos = strfind(url, SEARCH, false, pos))) { 85 | strdel(url, pos, pos + SUB_LEN); 86 | 87 | len -= SUB_LEN; 88 | 89 | if(REP_LEN && len + REP_LEN < maxlength) { 90 | strins(url, REPLACEMENT, pos, maxlength); 91 | 92 | pos += REP_LEN; 93 | len += REP_LEN; 94 | } 95 | } 96 | 97 | return count; 98 | } 99 | 100 | static stock InitYoutube() 101 | { 102 | for(new i = 0; i < MAX_YOUTUBE_SAVES; i++) 103 | { 104 | #if !defined __MAP_INCLUDED__ 105 | Youtube[i][RequestID] = Request:-1; 106 | #endif 107 | 108 | Youtube[i][PlaylistID] = INVALID_PLAYLIST_ID; 109 | } 110 | 111 | Converter = RequestsClient(CONVERTER_PATH, RequestHeaders("Authorization", "Bearer "API_KEY"")); 112 | } 113 | 114 | forward OnYoutubeVideoFinished(youtubeid); 115 | forward SongFinished(youtubeid); 116 | forward OnConvertResponse(Request:id, E_HTTP_STATUS:status, Node:node); 117 | forward OnSearchResponse(Request:id, E_HTTP_STATUS:status, Node:node); 118 | forward OnPlaylistInfoResponse(Request:id, E_HTTP_STATUS:status, Node:node); 119 | forward OnMVYoutubeError(youtubeid, const message[]); 120 | forward OnYoutubeVideoStart(youtubeid); 121 | forward OnPlaylistAddEntry(playerlistid, youtubeid); 122 | forward OnPlaylistFinished(playlistid); 123 | forward OnYoutubeSearch(playerid); 124 | 125 | stock IsValidYoutubeURL(const string[]) return (strfind(string,"youtube.com") != -1 && strfind(string,"watch?v=") != -1); 126 | stock IsYouTubeVideoPlaying(youtubeid) return youtubeid == INVALID_YT_ID ? false : Youtube[youtubeid][Playing]; 127 | stock GetPlaylistFromVideo(youtubeid) return youtubeid == INVALID_YT_ID ? INVALID_PLAYLIST_ID : Youtube[youtubeid][PlaylistID]; 128 | stock GetVideoDuration(youtubeid) return youtubeid == INVALID_YT_ID ? 0 : Youtube[youtubeid][Duration]; 129 | stock GetVideoTarget(youtubeid) return youtubeid == INVALID_YT_ID ? -1 : Youtube[youtubeid][PlayFor]; 130 | 131 | stock PlayYoutubeVideoFor(const url[], playfor = INVALID_PLAYER_ID, playlist = INVALID_PLAYLIST_ID, bool:usepos = false, Float:distance = 50.0) 132 | { 133 | new id = GetFreeIndex(); 134 | 135 | if(id != INVALID_YT_ID) 136 | { 137 | Youtube[id][PlayFor] = playfor; 138 | Youtube[id][Distance] = distance; 139 | Youtube[id][UsePos] = usepos; 140 | Youtube[id][PlaylistID] = playlist; 141 | 142 | format(Youtube[id][Link],256,"%s",url); 143 | 144 | if (playlist == INVALID_PLAYLIST_ID) 145 | { 146 | new const Request:ID = RequestJSON(Converter, "convert", HTTP_METHOD_POST, "OnConvertResponse", JsonObject("url", JsonString(url)), .headers = RequestHeaders()); 147 | 148 | #if defined __MAP_INCLUDED__ 149 | MAP_insert_val_val(LoadRequestToYoutubeID, _:ID, id); 150 | #else 151 | Youtube[id][RequestID] = ID; 152 | #endif 153 | } 154 | else 155 | { 156 | new const Request:ID = RequestJSON(Converter, "info", HTTP_METHOD_GET, "OnPlaylistInfoResponse", JsonObject("q", JsonString(url)), .headers = RequestHeaders()); 157 | 158 | #if defined __MAP_INCLUDED__ 159 | MAP_insert_val_val(LoadRequestToYoutubeID, _:ID, id); 160 | #else 161 | Youtube[id][RequestID] = ID; 162 | #endif 163 | } 164 | } 165 | return id; 166 | } 167 | 168 | stock SearchYoutubeVideos(playerid, const searchquery[]) 169 | { 170 | new requestlink[512]; 171 | format(requestlink, sizeof(requestlink), "search?q=%s&max_results=%i", searchquery, MAX_SEARCH_RESULTS); 172 | EncodeYoutubeURL(requestlink); 173 | 174 | new const Request:ID = RequestJSON(Converter, requestlink, HTTP_METHOD_GET, "OnSearchResponse", .headers = RequestHeaders()); 175 | #if defined __MAP_INCLUDED__ 176 | MAP_insert_val_val(LoadRequestToPlayerID, _:ID, playerid); 177 | #else 178 | PlayerIDToRequest[playerid] = ID; 179 | #endif 180 | } 181 | 182 | stock GetFreeIndex() 183 | { 184 | for(new i = 0; i < MAX_YOUTUBE_SAVES; i++) 185 | { 186 | if(!Youtube[i][Playing] && Youtube[i][PlaylistID] == INVALID_PLAYLIST_ID) return i; 187 | } 188 | 189 | print("[Warning] MV_Youtube: MAX_YOUTUBE_SAVES reached!"); 190 | return INVALID_YT_ID; 191 | } 192 | 193 | stock StopYoutubeVideo(youtubeid) 194 | { 195 | Youtube[youtubeid][Playing] = false; 196 | KillTimer(Youtube[youtubeid][Timer]); 197 | 198 | if(Youtube[youtubeid][PlayFor] == INVALID_PLAYER_ID) 199 | { 200 | #if defined _INC_open_mp 201 | for (new i = 0; i < MAX_PLAYERS; i++) 202 | #else 203 | for(new i = 0, j = GetPlayerPoolSize(); i <= j; i++) 204 | #endif 205 | { 206 | if(!IsPlayerConnected(i)) continue; 207 | StopAudioStreamForPlayer(i); 208 | } 209 | } 210 | else 211 | StopAudioStreamForPlayer(Youtube[youtubeid][PlayFor]); 212 | } 213 | 214 | stock GetVideoTitle(youtubeid) 215 | { 216 | new str[256]; 217 | strins(str, Youtube[youtubeid][Title], 0); 218 | return str; 219 | } 220 | 221 | stock GetVideoLink(youtubeid) 222 | { 223 | new str[256]; 224 | strins(str, Youtube[youtubeid][Link], 0); 225 | return str; 226 | } 227 | 228 | stock GetVideoStreamLink(youtubeid) 229 | { 230 | new str[256]; 231 | strins(str, Youtube[youtubeid][StreamLink], 0); 232 | return str; 233 | } 234 | 235 | stock CreatePlaylist(const name[]) 236 | { 237 | for(new i = 0; i < MAX_PLAYLISTS; i++) 238 | { 239 | if(Playlist[i][Active]) continue; 240 | Playlist[i][Active] = true; 241 | format(Playlist[i][Name], 32, "%s", name); 242 | return i; 243 | } 244 | 245 | print("[Warning] MV_Youtube: MAX_PLAYLISTS reached!"); 246 | return INVALID_PLAYLIST_ID; 247 | } 248 | 249 | stock RemovePlaylist(playlistid) 250 | { 251 | if(playlistid != INVALID_PLAYLIST_ID) 252 | { 253 | Playlist[playlistid][Active] = false; 254 | Playlist[playlistid][Name] = EOS; 255 | 256 | for(new i = 0; i < MAX_YOUTUBE_SAVES; i++) 257 | if(Youtube[i][PlaylistID] == playlistid) 258 | Youtube[i][PlaylistID] = INVALID_PLAYLIST_ID; 259 | } 260 | } 261 | 262 | stock GetPlaylistName(playlistid) 263 | { 264 | new str[32]; 265 | strins(str, Playlist[playlistid][Name], 0); 266 | return str; 267 | } 268 | 269 | stock GetPlaylistSongs(playlistid) 270 | { 271 | new songs[MAX_YOUTUBE_SAVES], index = 0; 272 | 273 | for(new i = 0; i < MAX_YOUTUBE_SAVES; i++) songs[i] = INVALID_YT_ID; 274 | for(new i = 0; i < MAX_YOUTUBE_SAVES; i++) 275 | { 276 | if(Youtube[i][PlaylistID] != playlistid) continue; 277 | songs[index] = i; 278 | index++; 279 | } 280 | return songs; 281 | } 282 | 283 | stock GetPlaylistSongsCount(playlistid) 284 | { 285 | new count = 0; 286 | 287 | for(new i = 0; i < MAX_YOUTUBE_SAVES; i++) 288 | { 289 | if(Youtube[i][PlaylistID] != playlistid) continue; 290 | count++; 291 | } 292 | 293 | return count; 294 | } 295 | 296 | PlayYoutubeVideo(youtubeid) //shouldn't use this function outside of the include. 297 | { 298 | new Float:pos[3]; 299 | 300 | Youtube[youtubeid][Playing] = true; 301 | 302 | CallLocalFunction("OnYoutubeVideoStart", "i", youtubeid); 303 | 304 | if(Youtube[youtubeid][PlayFor] == INVALID_PLAYER_ID) 305 | { 306 | #if defined _INC_open_mp 307 | for (new i = 0; i < MAX_PLAYERS; i++) 308 | #else 309 | for(new i = 0, j = GetPlayerPoolSize(); i <= j; i++) 310 | #endif 311 | { 312 | if(IsPlayerConnected(i)) 313 | { 314 | if(Youtube[youtubeid][UsePos]) GetPlayerPos(i, pos[0], pos[1], pos[2]); 315 | PlayAudioStreamForPlayer(i, Youtube[youtubeid][StreamLink], pos[0], pos[1], pos[2], Youtube[youtubeid][Distance], Youtube[youtubeid][UsePos]); 316 | } 317 | } 318 | } 319 | else 320 | { 321 | if(Youtube[youtubeid][UsePos]) GetPlayerPos(Youtube[youtubeid][PlayFor], pos[0], pos[1], pos[2]); 322 | PlayAudioStreamForPlayer(Youtube[youtubeid][PlayFor], Youtube[youtubeid][StreamLink], pos[0], pos[1], pos[2], Youtube[youtubeid][Distance], Youtube[youtubeid][UsePos]); 323 | } 324 | 325 | Youtube[youtubeid][Timer] = SetTimerEx("SongFinished",GetVideoDuration(youtubeid)*1000,false,"i",youtubeid); 326 | } 327 | 328 | public OnConvertResponse(Request:id, E_HTTP_STATUS:status, Node:node) 329 | { 330 | #if defined __MAP_INCLUDED__ 331 | new index = MAP_get_val_val(LoadRequestToYoutubeID, _:id); 332 | MAP_remove_val(LoadRequestToYoutubeID, _:id); 333 | #else 334 | new index = -1; 335 | 336 | for(new i = 0; i < sizeof(Youtube); i++) 337 | { 338 | if(Youtube[i][RequestID] == id) 339 | { 340 | Youtube[i][RequestID] = Request:-1; 341 | index = i; 342 | break; 343 | } 344 | } 345 | #endif 346 | 347 | if(status != HTTP_STATUS_OK) 348 | { 349 | new string[512], message[128]; 350 | 351 | JsonGetString(node, "message", message); 352 | 353 | format(string, sizeof(string), "%s - response code: %i - message: %s", GetError(status), _:status, message); 354 | CallLocalFunction("OnMVYoutubeError", "is", index, string); 355 | 356 | Youtube[index][PlaylistID] = INVALID_PLAYLIST_ID; 357 | return 0; 358 | } 359 | 360 | if (Youtube[index][PlaylistID] == INVALID_PLAYLIST_ID) 361 | { 362 | JsonGetString(node, "title", Youtube[index][Title], 256); 363 | JsonGetInt(node, "duration", Youtube[index][Duration]); 364 | JsonGetString(node, "youtube_id", Youtube[index][ytID], 16); 365 | } 366 | 367 | JsonGetString(node, "file", Youtube[index][StreamLink], 256); 368 | 369 | PlayYoutubeVideo(index); 370 | return 1; 371 | } 372 | 373 | public OnPlaylistInfoResponse(Request:id, E_HTTP_STATUS:status, Node:node) 374 | { 375 | #if defined __MAP_INCLUDED__ 376 | new index = MAP_get_val_val(LoadRequestToPlayerID, _:id); 377 | MAP_remove_val(LoadRequestToPlayerID, _:id); 378 | #else 379 | new index = -1; 380 | 381 | for(new i = 0; i < sizeof(Youtube); i++) 382 | { 383 | if (Youtube[i][RequestID] == id) 384 | { 385 | Youtube[i][RequestID] = Request:-1; 386 | index = i; 387 | break; 388 | } 389 | } 390 | #endif 391 | 392 | new string[256]; 393 | 394 | if(status != HTTP_STATUS_OK) 395 | { 396 | format(string, sizeof(string), "%s - response code: %i", GetError(status), _:status); 397 | CallLocalFunction("OnMVYoutubeError", "is", index, string); 398 | return 0; 399 | } 400 | 401 | JsonGetString(node, "title", Youtube[index][Title], 256); 402 | JsonGetInt(node, "duration", Youtube[index][Duration]); 403 | JsonGetString(node, "id", Youtube[index][ytID], 16); 404 | JsonGetString(node, "url", Youtube[index][Link], 256); 405 | 406 | CallLocalFunction("OnPlaylistAddEntry", "ii", Youtube[index][PlaylistID], index); 407 | 408 | if(GetPlaylistSongsCount(Youtube[index][PlaylistID]) == 1) 409 | { 410 | new const Request:ID = RequestJSON(Converter, "convert", HTTP_METHOD_POST, "OnConvertResponse", JsonObject("url", JsonString(Youtube[index][Link])), .headers = RequestHeaders()); 411 | 412 | #if defined __MAP_INCLUDED__ 413 | MAP_insert_val_val(LoadRequestToYoutubeID, _:ID, id); 414 | #else 415 | Youtube[index][RequestID] = ID; 416 | #endif 417 | } 418 | 419 | return 1; 420 | } 421 | 422 | public OnSearchResponse(Request:id, E_HTTP_STATUS:status, Node:node) 423 | { 424 | #if defined __MAP_INCLUDED__ 425 | new index = MAP_get_val_val(LoadRequestToPlayerID, _:id); 426 | MAP_remove_val(LoadRequestToPlayerID, _:id); 427 | #else 428 | new index = -1; 429 | 430 | for(new i = 0; i < sizeof(PlayerIDToRequest); i++) 431 | { 432 | if(PlayerIDToRequest[i] == id) 433 | { 434 | PlayerIDToRequest[i] = Request:-1; 435 | index = i; 436 | break; 437 | } 438 | } 439 | #endif 440 | 441 | new string[256]; 442 | 443 | if(status != HTTP_STATUS_OK) 444 | { 445 | format(string, sizeof(string), "%s - response code: %i", GetError(status), _:status); 446 | CallLocalFunction("OnMVYoutubeError", "is", index, string); 447 | return 0; 448 | } 449 | 450 | new bool:error; 451 | JsonGetBool(node, "error", error); 452 | 453 | if(error) 454 | { 455 | JsonGetString(node, "message", string); 456 | CallLocalFunction("OnMVYoutubeError", "is", index, string); 457 | return 0; 458 | } 459 | 460 | JsonToggleGC(node, false); 461 | 462 | new Node:results, length; 463 | 464 | JsonGetArray(node, "results", results); 465 | JsonArrayLength(results, length); 466 | 467 | for(new i = 0; i < length; i++) 468 | { 469 | new Node:result; 470 | JsonArrayObject(results, i, result); 471 | 472 | JsonGetString(result, "title", SearchResults[index][i][Title], 128); 473 | JsonGetString(result, "full_link", SearchResults[index][i][Link], 128); 474 | } 475 | 476 | JsonToggleGC(node, true); 477 | 478 | CallLocalFunction("OnYoutubeSearch", "i", index); 479 | return 1; 480 | } 481 | 482 | public SongFinished(youtubeid) 483 | { 484 | StopYoutubeVideo(youtubeid); 485 | 486 | CallLocalFunction("OnYoutubeVideoFinished","i",youtubeid); 487 | 488 | if(Youtube[youtubeid][PlaylistID] != INVALID_PLAYLIST_ID) 489 | { 490 | new plist = Youtube[youtubeid][PlaylistID], bool:finished = true; 491 | Youtube[youtubeid][PlaylistID] = INVALID_PLAYLIST_ID; 492 | 493 | for(new i = 0; i < MAX_YOUTUBE_SAVES; i++) 494 | { 495 | if(Youtube[i][PlaylistID] != plist) continue; 496 | 497 | new const Request:ID = RequestJSON(Converter, "convert", HTTP_METHOD_POST, "OnConvertResponse", JsonObject("url", JsonString(Youtube[i][Link])), .headers = RequestHeaders()); 498 | 499 | #if defined __MAP_INCLUDED__ 500 | MAP_insert_val_val(LoadRequestToYoutubeID, _:ID, id); 501 | #else 502 | Youtube[i][RequestID] = ID; 503 | #endif 504 | 505 | finished = false; 506 | break; 507 | } 508 | 509 | if(finished) 510 | CallLocalFunction("OnPlaylistFinished", "i", plist); 511 | } 512 | } 513 | 514 | stock GetError({_, E_HTTP_STATUS}:val) 515 | { 516 | new error[32]; 517 | switch(val) 518 | { 519 | case 1: error = "Bad host"; 520 | case 2: error = "No socket"; 521 | case 3: error = "Can't connect"; 522 | case 4: error = "Can't write"; 523 | case 5: error = "Content too big"; 524 | case 6: error = "Malformed response"; 525 | case (_:HTTP_STATUS_MULTIPLE_CHOICES)..(_:HTTP_STATUS_PERMANENT_REDIRECT): error = "Redirection"; 526 | case (_:HTTP_STATUS_BAD_REQUEST)..(_:HTTP_STATUS_LEGAL_FAILURE): error = "Client error"; 527 | case (_:HTTP_STATUS_SERVER_ERROR)..(_:HTTP_STATUS_AUTH_REQUIRED): error = "Server error"; 528 | } 529 | return error; 530 | } 531 | 532 | #if defined FILTERSCRIPT 533 | public OnGameModeInit() 534 | { 535 | InitYoutube(); 536 | 537 | #if defined MV_OnGameModeInit 538 | return MV_OnGameModeInit(); 539 | #else 540 | return 1; 541 | #endif 542 | } 543 | 544 | #if defined _ALS_OnGameModeInit 545 | #undef OnGameModeInit 546 | #else 547 | #define _ALS_OnGameModeInit 548 | #endif 549 | 550 | #define OnGameModeInit MV_OnGameModeInit 551 | #if defined MV_OnGameModeInit 552 | forward MV_OnGameModeInit(); 553 | #endif 554 | #else 555 | public OnGameModeInit() 556 | { 557 | InitYoutube(); 558 | 559 | #if defined MV_OnGameModeInit 560 | return MV_OnGameModeInit(); 561 | #else 562 | return 1; 563 | #endif 564 | } 565 | 566 | #if defined _ALS_OnGameModeInit 567 | #undef OnGameModeInit 568 | #else 569 | #define _ALS_OnGameModeInit 570 | #endif 571 | 572 | #define OnGameModeInit MV_OnGameModeInit 573 | #if defined MV_OnGameModeInit 574 | forward MV_OnGameModeInit(); 575 | #endif 576 | #endif 577 | 578 | public OnRequestFailure(Request:id, errorCode, errorMessage[], len) 579 | { 580 | #if defined MV_OnRequestFailure 581 | MV_OnRequestFailure(id, errorCode, errorMessage, len); 582 | #endif 583 | } 584 | 585 | #if defined _ALS_OnRequestFailure 586 | #undef OnRequestFailure 587 | #else 588 | #define _ALS_OnRequestFailure 589 | #endif 590 | 591 | #define OnRequestFailure MV_OnRequestFailure 592 | #if defined MV_OnRequestFailure 593 | forward MV_OnRequestFailure(Request:id, errorCode, errorMessage[], len); 594 | #endif 595 | --------------------------------------------------------------------------------