Player Shoutcast/icecast

Partagez votre expérience de PureBasic avec les autres utilisateurs.
Avatar de l’utilisateur
MetalOS
Messages : 1508
Inscription : mar. 20/juin/2006 22:17
Localisation : Lorraine
Contact :

Player Shoutcast/icecast

Message par MetalOS »

Salut tous le monde je vous partage le code source d'un petit player qui permet d’écouter une radio shoutcast/icecast grâce à la bibliothèque BASS. L'exemple et normalement cross plateforme. Vous devrez télécharger la lib BASS et la placer à côté de l’exécutable pour faire fonctionner le code.

Pour windows je n'ai pas trouvé de version 64bits de la lib du coup j'ai utilisé PB 32bits pour compiler le code. Le player récupère le titre et la pochette via l'api itunes si elle est disponible. Je vous partage le code car c'est quelque chose que j'ai longtemp cherché à faire depuis que j'utilise PB. Ca me fait plaisirs de le partger.

Code : Tout sélectionner

; ------------------------------------------------------------
; By MetalOS
; Shoutcast Player – Cross‑Platform (Windows/macOS/Linux)
; PureBasic + BASS audio library (lecture réseau HTTP/ICY)
; - Lecture d'un flux Shoutcast/Icecast
; - Récupération du titre courant via /stats?json=1 ou 7.html
; - Recherche et affichage de la pochette (API iTunes)
; ------------------------------------------------------------
EnableExplicit

UsePNGImageDecoder()
UseJPEGImageDecoder()
UsePNGImageEncoder()
UseJPEGImageEncoder()

; --- Réseau ---
CompilerIf #PB_Compiler_Version < 600
  If InitNetwork() = 0
    MessageRequester("Erreur", "InitNetwork a échoué.")
    End
  EndIf
CompilerEndIf

; --- Sélection du nom de bibliothèque BASS selon l'OS ---
CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Windows
    #BASS_LIB$ = "bass.dll"
  CompilerCase #PB_OS_MacOS
    #BASS_LIB$ = "libbass.dylib"
  CompilerCase #PB_OS_Linux
    #BASS_LIB$ = "libbass.so"
CompilerEndSelect

; --- Chargement dynamique de BASS ---
Global gLibBASS = 0

Prototype.i PB_BASS_Init(device.l, freq.l, flags.l, win.i, cls.i)
Prototype.i PB_BASS_Free()
Prototype.i PB_BASS_ErrorGetCode()
Prototype.i PB_BASS_StreamCreateURL(url.p-ascii, offset.l, flags.l, proc.i, user.i)
Prototype.i PB_BASS_StreamFree(handle.i)
Prototype.i PB_BASS_ChannelPlay(handle.i, restart.l)
Prototype.i PB_BASS_ChannelStop(handle.i)
Prototype.i PB_BASS_ChannelSetAttribute(handle.i, attrib.l, value.f)

Global BASS_Init.PB_BASS_Init
Global BASS_Free.PB_BASS_Free
Global BASS_ErrorGetCode.PB_BASS_ErrorGetCode
Global BASS_StreamCreateURL.PB_BASS_StreamCreateURL
Global BASS_StreamFree.PB_BASS_StreamFree
Global BASS_ChannelPlay.PB_BASS_ChannelPlay
Global BASS_ChannelStop.PB_BASS_ChannelStop
Global BASS_ChannelSetAttribute.PB_BASS_ChannelSetAttribute

Procedure.i LoadBASS()
  Protected libPath$ = GetPathPart(ProgramFilename()) + #BASS_LIB$
  If FileSize(libPath$) <= 0
    libPath$ = #BASS_LIB$
  EndIf
  gLibBASS = OpenLibrary(#PB_Any, libPath$)
  If gLibBASS = 0 : ProcedureReturn 0 : EndIf
  
  BASS_Init               = GetFunction(gLibBASS, "BASS_Init")
  BASS_Free               = GetFunction(gLibBASS, "BASS_Free")
  BASS_ErrorGetCode       = GetFunction(gLibBASS, "BASS_ErrorGetCode")
  BASS_StreamCreateURL    = GetFunction(gLibBASS, "BASS_StreamCreateURL")
  BASS_StreamFree         = GetFunction(gLibBASS, "BASS_StreamFree")
  BASS_ChannelPlay        = GetFunction(gLibBASS, "BASS_ChannelPlay")
  BASS_ChannelStop        = GetFunction(gLibBASS, "BASS_ChannelStop")
  BASS_ChannelSetAttribute= GetFunction(gLibBASS, "BASS_ChannelSetAttribute")
  
  If BASS_Init = 0 Or BASS_Free = 0 Or BASS_StreamCreateURL = 0 Or BASS_StreamFree = 0 Or BASS_ChannelPlay = 0 Or BASS_ChannelStop = 0 Or BASS_ChannelSetAttribute = 0
    CloseLibrary(gLibBASS) : gLibBASS = 0 : ProcedureReturn 0
  EndIf
  ProcedureReturn 1
EndProcedure

; --- Constantes UI ---
#WIN            = 0
#TIMER_META     = 1
#G_URL_LABEL    = 10
#G_URL          = 11
#G_BTN_PLAY     = 12
#G_BTN_STOP     = 13
#G_VOL_LABEL    = 14
#G_VOL          = 15
#G_TITLE        = 16
#G_COVER        = 17

; --- Globals ---
Global gStreamURL$ = "http://65.108.105.26:7551/;stream/1" ; exemple Radio Bizzz
Global gBaseURL$, gLastTitle$ = "", gCoverImage = 0
Global gStreamHandle = 0

; --- Helpers ---
Procedure.s TrimAll(s$) : ProcedureReturn Trim(ReplaceString(ReplaceString(s$, #CRLF$, " "), #TAB$, " ")) : EndProcedure

Procedure.s ExtractBaseURL(url$)
  Protected p, p2, base$
  p = FindString(url$, "://", 1)
  If p
    p2 = FindString(url$, "/", p+3)
  Else
    p2 = FindString(url$, "/", 1)
  EndIf
  If p2 : base$ = Left(url$, p2-1) : Else : base$ = url$ : EndIf
  ProcedureReturn base$
EndProcedure

Procedure.s SimpleURLEncode(s$)
  Protected i, c, out$
  For i=1 To Len(s$)
    c = Asc(Mid(s$, i, 1))
    Select c
      Case 48 To 57, 65 To 90, 97 To 122 : out$ + Chr(c)
      Case 32 : out$ + "+"
      Default : out$ + "%" + RSet(Hex(c), 2, "0")
    EndSelect
  Next
  ProcedureReturn out$
EndProcedure

Procedure.s GetJSONStringSafe(JSON, key$)
  Protected root = JSONValue(JSON)
  If root
    Protected node = GetJSONMember(root, key$)
    If node : ProcedureReturn GetJSONString(node) : EndIf
  EndIf
  ProcedureReturn ""
EndProcedure

; --- Récupération du titre courant depuis le serveur ---
Procedure.s FetchSongTitleFromStatsJSON(base$)
  Protected *mem, txt$, j, root, title$, arr, el
  *mem = ReceiveHTTPMemory(base$ + "/stats?json=1")
  If *mem
    txt$ = PeekS(*mem, -1, #PB_UTF8) : FreeMemory(*mem)
    If txt$ <> ""
      j = ParseJSON(#PB_Any, txt$)
      If j
        title$ = GetJSONStringSafe(j, "songtitle")
        If title$ = "" : title$ = GetJSONStringSafe(j, "song") : EndIf
        If title$ = ""
          root = JSONValue(j)
          arr = GetJSONMember(root, "streams")
          If arr And JSONType(arr) = #PB_JSON_Array And JSONArraySize(arr) > 0
            el = GetJSONElement(arr, 0)
            title$ = GetJSONString(GetJSONMember(el, "songtitle"))
            If title$ = "" : title$ = GetJSONString(GetJSONMember(el, "song")) : EndIf
          EndIf
        EndIf
        FreeJSON(j)
      EndIf
    EndIf
  EndIf
  ProcedureReturn TrimAll(title$)
EndProcedure

Procedure.s FetchSongTitleFrom7(base$)
  Protected *mem, txt$, title$, commas
  *mem = ReceiveHTTPMemory(base$ + "/7.html")
  If *mem
    txt$ = PeekS(*mem, -1, #PB_UTF8) : FreeMemory(*mem)
    If txt$ <> ""
      commas = CountString(txt$, ",")
      If commas >= 5 : title$ = StringField(txt$, 6, ",") : EndIf
      If title$ = ""
        *mem = ReceiveHTTPMemory(base$ + "/7.html?sid=1")
        If *mem
          txt$ = PeekS(*mem, -1, #PB_UTF8) : FreeMemory(*mem)
          commas = CountString(txt$, ",")
          If commas >= 5 : title$ = StringField(txt$, 6, ",") : EndIf
        EndIf
      EndIf
      If title$ = ""
        Protected pos = FindString(txt$, "StreamTitle='", 1)
        If pos
          Protected start = pos + Len("StreamTitle='")
          Protected stop = FindString(txt$, "';", start)
          If stop > start : title$ = Mid(txt$, start, stop-start) : EndIf
        EndIf
      EndIf
    EndIf
  EndIf
  ProcedureReturn TrimAll(title$)
EndProcedure

Procedure.s GetCurrentTitle(base$)
  Protected t$ = FetchSongTitleFromStatsJSON(base$)
  If t$ = "" : t$ = FetchSongTitleFrom7(base$) : EndIf
  ProcedureReturn t$
EndProcedure

; --- Téléchargement d'image ---
Procedure.i DownloadImageFromURL(url$)
  Protected *mem = ReceiveHTTPMemory(url$)
  If *mem
    Protected img = CatchImage(#PB_Any, *mem, MemorySize(*mem))
    FreeMemory(*mem)
    ProcedureReturn img
  EndIf
  ProcedureReturn 0
EndProcedure

; --- Recherche de pochette via iTunes ---
Procedure.s FindArtworkURL(artist$, title$)
  Protected term$ = SimpleURLEncode(artist$ + " " + title$)
  Protected url$  = "https://itunes.apple.com/search?term=" + term$ + "&media=music&entity=song&limit=1"
  Protected *mem = ReceiveHTTPMemory(url$)
  If *mem
    Protected txt$ = PeekS(*mem, -1, #PB_UTF8) : FreeMemory(*mem)
    If txt$ <> ""
      Protected j = ParseJSON(#PB_Any, txt$)
      If j
        Protected root = JSONValue(j)
        Protected results = GetJSONMember(root, "results")
        If results And JSONType(results) = #PB_JSON_Array And JSONArraySize(results) > 0
          Protected item = GetJSONElement(results, 0)
          Protected art$ = GetJSONString(GetJSONMember(item, "artworkUrl100"))
          If art$ <> ""
            art$ = ReplaceString(art$, "100x100", "600x600")
            FreeJSON(j)
            ProcedureReturn art$
          EndIf
        EndIf
        FreeJSON(j)
      EndIf
    EndIf
  EndIf
  ProcedureReturn ""
EndProcedure

Procedure UpdateCoverFromTitle(title$)
  Protected artist$, track$, dashPos
  dashPos = FindString(title$, " - ", 1)
  If dashPos
    artist$ = Trim(Mid(title$, 1, dashPos-1))
    track$  = Trim(Mid(title$, dashPos+3))
  Else
    track$  = title$
  EndIf
  If artist$ = "" And track$ = "" : ProcedureReturn : EndIf
  
  Protected artURL$ = FindArtworkURL(artist$, track$)
  If artURL$ <> ""
    Protected img = DownloadImageFromURL(artURL$)
    If img
      If gCoverImage : FreeImage(gCoverImage) : gCoverImage = 0 : EndIf
      gCoverImage = img
      SetGadgetState(#G_COVER, ImageID(gCoverImage))
    EndIf
  EndIf
EndProcedure

; --- Métadonnées ---
Procedure UpdateMeta()
  Protected t$ = GetCurrentTitle(gBaseURL$)
  If t$ <> "" And t$ <> gLastTitle$
    gLastTitle$ = t$
    SetGadgetText(#G_TITLE, t$)
    SetWindowTitle(#WIN, "Shoutcast Player - " + t$)
    UpdateCoverFromTitle(t$)
  EndIf
EndProcedure

; --- Audio : contrôle BASS ---
Procedure StartPlayback()
  Protected url$ = GetGadgetText(#G_URL)
  If url$ = "" : url$ = gStreamURL$ : SetGadgetText(#G_URL, url$) : EndIf
  gBaseURL$ = ExtractBaseURL(url$)
  
  If gStreamHandle
    BASS_ChannelStop(gStreamHandle)
    BASS_StreamFree(gStreamHandle)
    gStreamHandle = 0
  EndIf
  
  gStreamHandle = BASS_StreamCreateURL(url$, 0, 0, 0, 0)
  If gStreamHandle = 0
    MessageRequester("Erreur", "Impossible d'ouvrir le flux (code " + Str(BASS_ErrorGetCode()) + ")")
    ProcedureReturn
  EndIf
  
  BASS_ChannelPlay(gStreamHandle, 0)
  
  ; Volume initial selon le TrackBar
  Protected vol.f = GetGadgetState(#G_VOL) / 100.0
  If vol < 0 : vol = 0.7 : EndIf
  ; BASS_ATTRIB_VOL = 2 (valeur 0.0 à 1.0)
  BASS_ChannelSetAttribute(gStreamHandle, 2, vol)
  
  UpdateMeta()
EndProcedure

Procedure StopPlayback()
  If gStreamHandle
    BASS_ChannelStop(gStreamHandle)
    BASS_StreamFree(gStreamHandle)
    gStreamHandle = 0
  EndIf
EndProcedure

; --- Programme principal ---
If LoadBASS() = 0
  MessageRequester("BASS manquant", "La bibliothèque '" + #BASS_LIB$ + "' est introuvable. Place-la à côté de l'exécutable ou dans un chemin système.")
  End
EndIf

If BASS_Init(-1, 44100, 0, 0, 0) = 0
  MessageRequester("Erreur", "BASS_Init a échoué (code " + Str(BASS_ErrorGetCode()) + ")")
  End
EndIf

If OpenWindow(#WIN, 0, 0, 520, 460, "Shoutcast Player (PureBasic · BASS)", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget)
  TextGadget(#G_URL_LABEL, 10, 12, 60, 20, "Flux URL:")
  StringGadget(#G_URL, 75, 10, 430, 22, gStreamURL$)
  ButtonGadget(#G_BTN_PLAY, 10, 45, 80, 28, "Lire")
  ButtonGadget(#G_BTN_STOP, 95, 45, 80, 28, "Stop")
  TextGadget(#G_VOL_LABEL, 185, 50, 50, 20, "Volume")
  TrackBarGadget(#G_VOL, 235, 50, 270, 22, 0, 100)
  SetGadgetState(#G_VOL, 70)

  TextGadget(#G_TITLE, 10, 85, 495, 24, "Titre en cours: (inconnu)")
  ImageGadget(#G_COVER, 110, 120, 300, 300, 0, #PB_Image_Border)

  AddWindowTimer(#WIN, #TIMER_META, 10000)

  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        Break
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #G_BTN_PLAY
            StartPlayback()
          Case #G_BTN_STOP
            StopPlayback()
          Case #G_VOL
            If gStreamHandle
              BASS_ChannelSetAttribute(gStreamHandle, 2, GetGadgetState(#G_VOL)/100.0)
            EndIf
        EndSelect
      Case #PB_Event_Timer
        If EventTimer() = #TIMER_META
          UpdateMeta()
        EndIf
    EndSelect
  ForEver

  If gCoverImage : FreeImage(gCoverImage) : EndIf
EndIf

If gStreamHandle : BASS_ChannelStop(gStreamHandle) : BASS_StreamFree(gStreamHandle) : EndIf
BASS_Free()

End
Bonne utilisation
Avatar de l’utilisateur
venom
Messages : 3136
Inscription : jeu. 29/juil./2004 16:33
Localisation : Klyntar
Contact :

Re: Player Shoutcast/icecast

Message par venom »

Encore mieux 8) merci du partage c'est cool.

Pour la lib x64 tu la trouve sur le site officiel mais caché dans l'archive 32Bits :wink:






@++
Windows 10 x64, PureBasic 5.73 x86 & x64
GPU : radeon HD6370M, CPU : p6200 2.13Ghz
Avatar de l’utilisateur
MetalOS
Messages : 1508
Inscription : mar. 20/juin/2006 22:17
Localisation : Lorraine
Contact :

Re: Player Shoutcast/icecast

Message par MetalOS »

Super merci je vais regarder.
Avatar de l’utilisateur
SPH
Messages : 4944
Inscription : mer. 09/nov./2005 9:53

Re: Player Shoutcast/icecast

Message par SPH »

Merci.

Cela fonctionne avec bass 32b mais pas avec bass 64

et sur w11 (pas essayé sur les autres wiwin), on ne vois pas l'image entière. On ne vois que "therio"...

!i!i!i!i!i!i!i!i!i!
!i!i!i!i!i!i!
!i!i!i!
//// Informations ////
Intel Core i7 4770 64 bits - GTX 650 Ti
Version de PB : 6.12LTS- 64 bits
Avatar de l’utilisateur
MetalOS
Messages : 1508
Inscription : mar. 20/juin/2006 22:17
Localisation : Lorraine
Contact :

Re: Player Shoutcast/icecast

Message par MetalOS »

Ouais il y a surement des ajustements à faire. Je n’arrive pas à le faire fonctionner avec BASS64 non plus.
Avatar de l’utilisateur
venom
Messages : 3136
Inscription : jeu. 29/juil./2004 16:33
Localisation : Klyntar
Contact :

Re: Player Shoutcast/icecast

Message par venom »

MetalOS a écrit : lun. 18/août/2025 22:34 Ouais il y a surement des ajustements à faire. Je n’arrive pas à le faire fonctionner avec BASS64 non plus.
Tu as laissé tes Prototype en .i ou tu as mis .l ?







@++
Windows 10 x64, PureBasic 5.73 x86 & x64
GPU : radeon HD6370M, CPU : p6200 2.13Ghz
Avatar de l’utilisateur
MetalOS
Messages : 1508
Inscription : mar. 20/juin/2006 22:17
Localisation : Lorraine
Contact :

Re: Player Shoutcast/icecast

Message par MetalOS »

En point i. Sur mac en 64 bits ça fonctionne
Avatar de l’utilisateur
venom
Messages : 3136
Inscription : jeu. 29/juil./2004 16:33
Localisation : Klyntar
Contact :

Re: Player Shoutcast/icecast

Message par venom »

Oui apparemment malgré le x64 il faut laisser .i

Tu vas pouvoir sortir les 2 versions alors :wink: 8)






@++
Windows 10 x64, PureBasic 5.73 x86 & x64
GPU : radeon HD6370M, CPU : p6200 2.13Ghz
Avatar de l’utilisateur
MetalOS
Messages : 1508
Inscription : mar. 20/juin/2006 22:17
Localisation : Lorraine
Contact :

Re: Player Shoutcast/icecast

Message par MetalOS »

Je vais essayer :D
Avatar de l’utilisateur
Philippe_GEORGES
Messages : 140
Inscription : mer. 28/janv./2009 13:28

Re: Player Shoutcast/icecast

Message par Philippe_GEORGES »

Merci pour le partage. Un bon exemple de code et d'utilisation des DLL !

Phil
Philippe GEORGES
"La simplicité est la sophistication suprême" (De Vinci)
assistance informatique, création de logiciels
georges.informatique@gmail.com
Avatar de l’utilisateur
venom
Messages : 3136
Inscription : jeu. 29/juil./2004 16:33
Localisation : Klyntar
Contact :

Re: Player Shoutcast/icecast

Message par venom »

MetalOS a écrit : mer. 20/août/2025 14:27 Je vais essayer :D
J'ai compiler en X64 avec bass 64bits.dll sous windows tout roule sans erreur :wink:







@++
Windows 10 x64, PureBasic 5.73 x86 & x64
GPU : radeon HD6370M, CPU : p6200 2.13Ghz
Avatar de l’utilisateur
MetalOS
Messages : 1508
Inscription : mar. 20/juin/2006 22:17
Localisation : Lorraine
Contact :

Re: Player Shoutcast/icecast

Message par MetalOS »

Merci @Philippe_GEORGES

@venom cool bonne nouvelle je vais pouvoir passer mon player en 64bits.
Répondre