soundplayer — n_pbt_soundplayer #
← Component reference · Guide contents
Plays a sound or a piece of music — a file of the workstation or a URL — asynchronously (events) or synchronously (the call returns when the sound is over), and a pure beep without any file. Every format the WebView2 engine decodes: mp3, wav, ogg, opus, flac, aac, m4a, mp4, webm. No third-party DLL, no
PlaySoundlimited to wav.
▶ See it live — Demo application, Sound player tile: the sounds, the code that plays them and this page, side by side.
At a glance #
| Nonvisual object | n_pbt_soundplayer |
| Used for | A chime before a message, an alert, a waiting music, an mp3 picked by the user, a stream from the Internet |
| Principle | A hidden page carries a player; of_play returns at once and the events tell the rest, of_play_sync waits for the end of the sound |
| Dependency | The WebView2 runtime, already required by the library — nothing else |
Quick start #
// In the window : the object
n_pbt_soundplayer inv_sound
inv_sound = create n_pbt_soundplayer
// Synchronous : the next line runs after the chime
inv_sound.of_play_sync(/*source*/ "C:\Windows\Media\chimes.wav")
MessageBox("Orders", "Order 4152 shipped.")
// Asynchronous : a music from the Internet, the window stays alive
inv_sound.ii_volume = 60
inv_sound.of_play(/*source*/ "https://example.org/music/lobby.mp3")
// When the window closes : the hidden player goes with the object
destroy inv_sound
The ue_* events are delivered on their own — the component drains them itself while the player is open and raises them on the component: nothing to wire, no receiver and no timer.
Where to write the events #
An object made by create n_pbt_soundplayer serves the synchronous sounds very well, and those started without waiting for news — it is what the quick start does —, but its events have no script: PowerBuilder cannot write ue_ended on an object created in a script. To react to the events, two ways:
- the object placed in the window (or the user object): in the painter, insert a nonvisual object
n_pbt_soundplayerand name itinv_sound; its eventsue_started,ue_ended,ue_failed… are then scripted like a control's, and it is born and dies with the window — what the demo application does; - a descendant: inherit from
n_pbt_soundplayer(n_my_sounds), writeue_endedin that descendant, and create it withcreate n_my_sounds.
Do not destroy the player from one of its own events: of_close is enough, and destroying the object does it for you.
File or URL, and why a long piece starts at once #
- A file of the workstation is any path PowerBuilder can name — absolute, relative to the application folder, network. The DLL serves it to the player as a stream, in slices, with the range requests a player emits to move within the piece: nothing is loaded in memory, and
of_seekis immediate. - An
httpsURL is passed as it is: the sound starts once its beginning has arrived. Anhttp://address is refused at once (the method returns-5andof_get_last_error()says why; in the queue,ue_failedwithREASON_INSECURE, at its turn): the player's page is served over https, and the engine would upgrade the address to https or block it — an intranet server without TLS would look missing. Download it first (restclient.of_downloadto a temporary file). A media is not subject to CORS, unlike an API call — seerestclientfor that case. - A beep (
of_beep,of_beep_sync) needs no file: an oscillator of the audio engine, at the frequency and for the duration you give.
Properties #
| Property | Type | Default | Role |
|---|---|---|---|
ii_volume | integer | 100 | Volume in percent, 0 to 100, handed over with the verb that starts a sound — and it is the volume of THAT sound: of_play, of_play_sync and of_crossfade set the main sound; of_play_over, of_beep and of_play_named play their own at it without touching the main sound (an alert at 100 over a background lowered to 20); a sound queued by of_enqueue keeps it for its turn. Changed while a sound plays, it waits for the next verb — only of_fade_to changes the sound playing |
ib_loop | boolean | false | True to play the sound again and again until of_stop — a ring, a background music. Handed over with the next sound. of_play_sync ignores it: a synchronous sound plays once, a loop has no end to wait for. A looping sound never ends: the queue (of_enqueue) waits behind it until of_stop, of_next or of_stop_sound |
il_timeout_ms | long | 300000 | The longest a synchronous sound (of_play_sync, of_beep_sync, of_play_named_sync) may last; five minutes by default. Past it the call returns -4 and that sound is cut (ue_stopped follows) — that one only: a background, the overlays and the queue go on. 0 or less: 24 hours |
ii_rate | integer | 100 | Speed in percent (25 to 400), handed over with the verb that starts a sound, for THAT sound: the main sound, or an overlay (of_play_over) without changing the main one |
ii_pan | integer | 0 | Balance of the MAIN sound, from -100 (left) to 100 (right), handed over with of_play, of_play_sync or of_crossfade, for FILES; a URL plays unbalanced |
il_fade_ms | long | 0 | Fade in milliseconds (10,000 at most): every start rises from silence, every of_stop or pause dies out, a resume rises again; ue_stopped still comes at once |
ib_gapless_loop | boolean | false | With ib_loop, a SHORT file of the workstation loops without a gap: decoded once, it joins seamlessly — a ring, a music bed. 4 MB and 60 seconds at most (past that, ue_failed with REASON_TOO_LONG); a URL loops the ordinary way. Handed over with the verb, like ib_loop |
ib_muted | boolean | false | Every sound of the player silent at once — the main sound, the overlays, the beeps, the named sounds — ii_volume kept: false brings the sound back at the same level. Live |
ib_queue_repeat | boolean | false | The queue (of_enqueue) starts over after its last sound, again and again; of_next after the last goes to the first, of_previous before the first to the last. A queue whose sounds all fail stops anyway. Live |
ipo_owner | powerobject | null | The visual object this player works for: the licence is checked on its class. Needed only in the demonstration application; a development or runtime key unlocks the player without it. Not unlocked, the player runs in demo mode — only the first 10 seconds of a sound are played |
Methods #
| Method | Role |
|---|---|
of_get_last_error ( ) → string | Why the last call failed: missing file, undecodable format, http:// address, silent URL, no audio output, number of a sound no longer under way. Filled by ue_failed too, and after every -5. After an of_play_sync that returned 0: "stopped" when the sound was cut rather than played to its end |
of_open ( ) → long | Creates the player. Optional — every method does it — but calling it when the window opens pays the cost once. Returns the handle (> 0), or -2 when the player could not be created (of_get_last_error() says why) |
of_is_open ( ) → boolean | True once the player exists |
of_play (string as_source {, long al_start_ms}) → long | Plays without waiting: a file of the workstation or an https URL, the end comes through ue_ended. al_start_ms starts the sound that far in (in demo mode, never past the first 10 seconds). The main sound playing is replaced, without an event; a beep or a named sound goes on. Returns 0 once started, -5 when the source is refused at once — empty, an http:// address, a malformed file:// address, a format refused by its name: of_get_last_error() says which, the sound playing goes on and no ue_failed follows —, -2 when the player could not be created, -4 when the page did not answer |
of_play_sync (string as_source) → long | Plays and waits for the end: the call returns when the sound is over (or stopped). The window keeps painting, but past 250 ms every PBToolboxAI component of the application shows the busy cursor and ignores the mouse until the call returns: keep it for short sounds. Plays once, whatever ib_loop says. Returns 0 once the sound is over (of_get_last_error() = "stopped" when it was cut), -2 when the player could not be created, -4 when it could not play, when il_timeout_ms elapsed (that sound alone is cut) or when another synchronous sound is already waiting |
of_beep (long al_hz, long al_ms) → long | A pure tone without a file: al_hz (20 to 20,000) for al_ms milliseconds (10 to 5,000), at ii_volume; a value beyond a bound is brought back to it. A channel of its own: of_play does not cut it, of_stop does. Returns at once; ue_ended follows. Returns 0 once started, -5 when al_hz or al_ms is 0 or less (an uninitialised variable does not sound a default beep), -2 when the player could not be created, -4 when the page did not answer |
of_beep_sync (long al_hz, long al_ms) → long | The same tone, and the call returns once it is over: two in a row make a two-note signal. Returns 0, -5 when al_hz or al_ms is 0 or less, -2 when the player could not be created, or -4 without an audio output, when il_timeout_ms elapsed (the beep alone is cut) or when another synchronous sound is already waiting (of_get_last_error()) |
of_stop ( ) → long | Stops everything the player plays — the sound, a beep, a named sound, the overlays, a crossfade — and empties the queue; one ue_stopped follows, at once. With il_fade_ms, the main sound dies out over that time instead of stopping dead Returns 0 once done. |
of_pause ( ) → long | Suspends the sound where it is (over il_fade_ms); of_resume picks it up. Returns 0 once done, -5 when there is nothing to suspend — no main sound, or already paused; a beep or an overlay is not suspended —, -2 when the player could not be created, -4 when the page did not answer |
of_resume ( ) → long | Picks the sound up where of_pause left it. Returns 0 once done, -5 when no sound is paused, -2 when the player could not be created, -4 when the page did not answer |
of_seek (long al_ms) → long | Moves to a position of the sound, in milliseconds from its start. In demo mode, never past the first 10 seconds. To START from a position, of_play takes one: the player keeps no stopped sound to move in. Returns 0 once done, -5 when no sound is playing (nothing to move), -2 when the player could not be created, -4 when the page did not answer |
of_is_playing ( ) → boolean | True while a sound, a beep, a named sound or an overlay is under way — a paused sound counts, it has not finished |
of_is_paused ( ) → boolean | True while the sound is suspended by of_pause |
of_duration ( ) → long | The length of the sound in milliseconds, 0 while unknown; ue_started carries it too |
of_position ( ) → long | Where the sound is, in milliseconds — poll it from a timer to move a progress bar of yours |
of_source ( ) → string | What of_play received last, verbatim |
of_play_over (string as_source) → long | Plays a sound ON TOP of what plays, without cutting it, at ii_volume and ii_rate — its own: the background keeps its own (a background lowered by of_fade_to stays low under an alert at 100); of_stop_sound stops it alone, of_stop with the rest. Returns the number of the sound (> 0) — its events carry it, two overlays of the same file are told apart by it —, -5 when the source is refused at once, -2 when the player could not be created, -4 when the page did not answer |
of_enqueue (string as_source) → long | QUEUES a sound: played at once when the player is idle, after the current sound (or beep, or named sound) otherwise, never cut. The queued sound keeps ii_volume, ii_rate and ib_loop as they are at the call, for its turn; a looping sound never ends, the queue waits behind it. A sound that cannot play raises ue_failed at its turn and the queue goes on. The queue keeps what it played: of_next, of_previous. Returns the number of the sound (> 0), -2 when the player could not be created, -4 when the page did not answer |
of_clear_queue ( ) → long | Forgets the queue — the sounds waiting and those it already played —, without cutting the one playing; ue_queue_done still follows its end. Returns 0 once done, -2 when the player could not be created, -4 when the page did not answer |
of_queue_count ( ) → long | Returns the number of sounds still waiting |
of_preload (string as_source) → long | Loads a sound without playing it: the first of_play of that source starts at once. A file that turns out unreadable is reported by that of_play, through ue_failed. Returns 0 once done, -5 when the source is refused at once (of_get_last_error() says why), -2 when the player could not be created, -4 when the page did not answer |
of_fade_to (integer ai_volume, long al_ms) → long | Brings the volume to ai_volume (0 to 100) in al_ms ms on the sound playing; the target becomes ii_volume, and a sound played over it meanwhile does not interrupt the fade. Returns 0 once done, -2 when the player could not be created |
of_play_named (string as_name) → long | A SYNTHESISED sound, no file, at ii_volume: SOUND_SUCCESS, SOUND_ERROR, SOUND_WARNING, SOUND_INFO, SOUND_NOTIFY. A channel of its own: of_play does not cut it, of_stop does. Returns 0 once started, -2 when the player could not be created; an unknown name comes through ue_failed (REASON_UNKNOWN) |
of_play_named_sync (string as_name) → long | The same sound, and the call returns at its end. Returns 0, -2 when the player could not be created, -4 on an unknown name, without an audio output or when another synchronous sound is already waiting (of_get_last_error()) |
of_has_output ( ) → boolean | True when the workstation has an audio output at all: to ask BEFORE an alert. The sound always goes to the default output of Windows |
of_equalizer (boolean ab_on {, integer ai_gains[]}) → long | 5-band equalizer (60 / 230 / 910 / 3600 / 14000 Hz, of_eq_frequencies gives the centers), gain in dB from -24 to 24 (a value outside is brought back to the nearest bound); the overload sets all gains (a band the array does not reach goes back to 0), of_equalizer_band sets one live. LOCAL files. Returns 0 once done, -2 when the player could not be created |
of_equalizer_band (integer ai_band, integer ai_gain) → long | Sets the gain of ONE band (ai_band 1 to 5, ai_gain in dB, -24 to 24) and applies it at once to the sound playing — a slider of yours moved live; the other bands keep their gain. Returns 0, -5 when the band is out of range, -2 when the player could not be created |
of_eq_frequencies (ref long al_freqs[]) → long | Fills al_freqs with the center frequency of each band, in Hz and in order (60, 230, 910, 3600, 14000) — what a band label of yours shows. Returns how many bands there are, 5 |
of_meter (boolean ab_on {, integer ai_bars}) → long | Turns a VU meter / analyser on: then poll of_level (0 to 100) and of_spectrum (ai_bars bars) from a timer to draw your own visualiser. LOCAL files. Returns 0 once done, -5 when ai_bars is out of 1 to 64 (nothing is changed), -2 when the player could not be created |
of_level ( ) → long | Returns the loudness right now, 0 to 100, read live — a VU meter needle; 0 when the meter (of_meter) is off or nothing plays. Poll it from a timer |
of_spectrum (ref long al_bars[]) → long | Fills al_bars with the spectrum, read live: as many values as of_meter asked for, 0 to 100 each, low frequencies first — the bars of an analyser of yours. Returns how many bars there are, 0 with the meter off |
of_crossfade (string as_source, long al_ms) → long | Crossfade: the current sound dies out over al_ms (10,000 at most) while the new source rises — a change of track with no gap; 0 switches at once. The new sound takes ii_volume, ii_rate, ii_pan and ib_loop, and an of_fade_to during the rise takes it over. of_stop during the fade stops both, and an of_play_sync waiting for the first sound returns; the sound replaced raises nothing. Returns 0 once started, -5 when the source is refused at once — the sound playing then goes on, untouched —, -2 when the player could not be created, -4 when the page did not answer |
of_play_range (string as_source, long al_from_ms, long al_to_ms) → long | Plays a SEGMENT of a file, from al_from_ms to al_to_ms — a bank of short sounds in one file, each played by its bounds. Replaces the main sound like of_play, with its settings; with ib_loop the segment plays again from its start. ue_ended at its end. Returns 0 once started, -5 when al_from_ms is negative, al_to_ms not after it, or the source refused at once, -2 when the player could not be created, -4 when the page did not answer |
of_stop_sound (long al_sound) → long | Stops ONE sound by its number (of_play_over and of_enqueue return it, the events carry it): an overlay — the others go on —, the main sound (over il_fade_ms, the queue moving on to the next), a beep, a named sound; one ue_stopped follows. A sound still WAITING is taken out of the queue, without an event. Returns 0 once done, -5 when no sound has that number now, -2 when the player could not be created, -4 when the page did not answer |
of_next ( ) → long | Plays the NEXT sound of the queue now — the sound playing is replaced, without an event; with ib_queue_repeat, after the last comes the first. ue_source_changed says where the queue is. Returns 0 once done, -5 when there is no next sound, -2 when the player could not be created, -4 when the page did not answer |
of_previous ( ) → long | Plays the sound BEFORE the one playing in the queue — the queue keeps what it played; the queue idle, the last one played again. With ib_queue_repeat, before the first comes the last. Returns 0 once done, -5 when there is none, -2 when the player could not be created, -4 when the page did not answer |
of_queue_index ( ) → long | Returns the place in the queue (1 = the first sound queued) of the sound playing, 0 when that sound does not come from the queue or nothing plays |
of_is_buffering ( ) → boolean | True while the sound runs but waits for its data — the time ue_buffering announced: an Internet stream that stalls, a jump far into a file read from a URL. A file of the workstation never waits |
of_process_events ( ) | Drains the event queue and raises them on the object. The component's own pump calls it for you while the player is open — you never call it |
of_close ( ) → long | Releases the player, stopping the sound first, at once (no fade): a sound that was playing ends on its ue_stopped, raised before the call returns; done for you when the object is destroyed. Do not destroy the player from one of its events: of_close is enough Returns 0 once done. |
of_reset ( ) → long | Stops the sound and puts every setting back to its default Returns 0 once done. |
Events #
| Event | When |
|---|---|
ue_started (string as_source, long al_duration_ms, long al_sound) | The sound really starts — a URL, once enough data has arrived. al_duration_ms is the length when the file says it, 0 otherwise; al_sound is the number of the sound |
ue_ended (string as_source, boolean ab_truncated, long al_sound) | The sound is over, of itself. ab_truncated is true when the demo limit cut it, never with a licence. A looping sound never ends: it stops. A main sound REPLACED by another (of_play, of_crossfade, of_next…) raises nothing: your script replaced it |
ue_failed (string as_source, string as_message, string as_reason, long al_sound) | The sound could not play — a source refused at once by a method is its -5, not this event. as_reason is a word to test, a REASON_* constant: REASON_INSECURE (http:// address), REASON_FORMAT (format refused by its name), REASON_UNSUPPORTED (missing file, malformed file:// address), REASON_NETWORK, REASON_DECODE, REASON_NO_SOURCE, REASON_UNKNOWN (no such named sound), REASON_UNAVAILABLE (no audio output), REASON_TOO_LONG (a gapless loop of a file too long for it), REASON_FAILED (anything else, a script error of the page included); as_message is the sentence to show, in the display language; al_sound the number of the sound. A queued sound that fails does not stop the queue |
ue_stopped ( ) | Raised once when of_stop (or of_close, of_stop_sound, or the end of il_timeout_ms for the sound of that call) cut what was playing — not when a sound ends of itself, that is ue_ended |
ue_paused ( ) / ue_resumed ( ) | The sound is suspended, then picks up again |
ue_progress (long al_position_ms, long al_duration_ms) | Once a second of real time while playing, whatever ii_rate: position and length |
ue_queue_done ( ) | The last sound of the queue (of_enqueue) has ended — never with ib_queue_repeat, the queue starts over |
ue_buffering (boolean ab_on) | ab_on true: the sound runs but waits for its data — an Internet stream that stalls, a jump far into a file read from a URL; false: it runs again (or is paused, stopped, replaced, in error). A wait shorter than a quarter of a second says nothing |
ue_source_changed (long al_index, string as_source) | The queue moves to one of its sounds — of itself, by of_next, of_previous or ib_queue_repeat: al_index is its place in the queue (1 = the first sound queued), as_source what was queued |
Several sounds, fades, queue #
- The main channel and the overlay.
of_play,of_pause,of_seek,of_stopdrive ONE sound: a piece of music, a ringing.of_play_overplays a second one on top, cutting nothing — an alert over a background. Each verb sets ITS sound:ii_volumeandii_ratehanded over withof_play_overare the overlay's, not the background's — an alert at 100 over a background lowered to 20 byof_fade_to, the background stays at 20. Beeps and named sounds are channels of their own:of_playdoes not cut them, onlyof_stopcuts everything. A main sound replaced (of_play,of_crossfade,of_next) raises nothing: your script replaces it. - The number of a sound.
of_play_overandof_enqueuereturn a number (> 0), whichue_started,ue_endedandue_failedcarry inal_sound: two alerts of the same file are told apart, andof_stop_soundstops one of them alone. - The queue.
of_enqueuewaits for the end of the current sound;ue_queue_donesays when the last one is over. A sound that fails (ue_failed) does not silence the next ones. The queue keeps what it played:of_nextandof_previousmove in it,of_queue_indexsays where it is,ue_source_changedannounces each move,ib_queue_repeatstarts it over. A looping sound never ends: the queue waits behind it.of_playstays the verb that cuts. - The fades.
il_fade_ms(10,000 ms at most) makes every start rise from silence and everyof_stopor pause die out;of_fade_tolowers the background during an announcement. Nothing abrupt. - A loop without a gap.
ib_gapless_loopdecodes a short file of the workstation once and joins it seamlessly: the ring no longer leaves a gap at each lap. - A segment.
of_play_rangeplays a piece of a file, from one millisecond to another: a bank of sounds in a single file. - The mute.
ib_mutedsilences everything, live, without losingii_volume. - The named sounds.
of_play_named(SOUND_SUCCESS)and its four siblings are synthesised: nothing to ship, nothing to look for on the disk. - Before the alert.
of_preloadopens the file ahead,of_has_outputsays whether the workstation has an output at all. The sound goes to the default output of Windows: the engine gives the id of an output only to a page granted media access, which an ordinary workstation does not grant — so choosing an output is not offered. - A stream that stalls.
ue_bufferingsays when a sound waits for its data, then when it runs again;of_is_bufferingreads it. - The balance (
ii_pan) holds for files of the workstation. A URL plays unbalanced: the browser refuses to feed a sound from another site into its audio graph (CORS), and that is not a setting. - No recording. Capturing the microphone is another component; this one plays.
// A background in a loop, faded in ; an alert over it, the background lowered meanwhile
inv_sound.il_fade_ms = 1500
inv_sound.ib_loop = true
inv_sound.of_play(/*source*/ "C:\Windows\Media\Ring05.wav")
inv_sound.of_fade_to(/*volume*/ 20, /*ms*/ 500)
// The alert has its own volume : the background stays at 20
inv_sound.ii_volume = 100
inv_sound.of_play_over(/*source*/ "C:\Windows\Media\Alarm01.wav")
// Announcements in a row : each one waits for the previous
inv_sound.of_enqueue(/*source*/ "order-4152.mp3")
inv_sound.of_enqueue(/*source*/ "order-4153.mp3")
// A confirmation with no file at all
inv_sound.of_play_named(/*name*/ n_pbt_soundplayer.SOUND_SUCCESS)
// Local variables
long ll_first
// Two alerts of the same file : each one has its own number
ll_first = inv_sound.of_play_over(/*source*/ "C:\Windows\Media\Alarm01.wav")
inv_sound.of_play_over(/*source*/ "C:\Windows\Media\Alarm01.wav")
// Later : the first one stopped alone, the other goes on
inv_sound.of_stop_sound(/*sound*/ ll_first)
// A queue that starts over after its last sound, and the next sound right now
inv_sound.ib_queue_repeat = true
inv_sound.of_next()
// A ring that loops without a gap, then everything silent for a moment
inv_sound.ib_loop = true
inv_sound.ib_gapless_loop = true
inv_sound.of_play(/*source*/ "C:\Windows\Media\Ring05.wav")
inv_sound.ib_muted = true
Examples #
A two-note signal, without any file #
// Each call waits for its tone : the second starts when the first is over
inv_sound.of_beep_sync(/*hz*/ 660, /*ms*/ 180)
inv_sound.of_beep_sync(/*hz*/ 990, /*ms*/ 220)
An mp3 picked by the user, with a progress bar #
// Local variables
string ls_path, ls_file
constant unsignedlong OFN_EXPLORER = 2
constant unsignedlong OFN_NOCHANGEDIR = 64
// Let the user pick a music file - in the Explorer style, without moving the current folder of the application - then play it
if GetFileOpenName("Music", ls_path, ls_file, "mp3", "Music (*.mp3;*.wav;*.flac),*.mp3;*.wav;*.flac", /*initdir*/ "", OFN_EXPLORER + OFN_NOCHANGEDIR) = 1 then
inv_sound.of_play(/*source*/ ls_path) // returns at once ; ue_started brings the length
Timer(0.5) // in the window's timer event :
end if
// event timer of the window
if inv_sound.of_is_playing() then
hpb_progress.Position = inv_sound.of_position() * 100 / Max(inv_sound.of_duration(), 1)
end if
A ring in a loop until the user acts #
// A ring at 40 %, in a loop until it is stopped
inv_sound.ii_volume = 40
inv_sound.ib_loop = true
inv_sound.of_play(/*source*/ "C:\Windows\Media\Ring01.wav")
// ... later, in the button that acknowledges the call :
inv_sound.of_stop()
Good practice #
- One object per window, opened with it (
of_open): the hidden player costs a few hundred milliseconds the first time, nothing afterwards. of_play_syncblocks your script, and the mouse past 250 ms: the window repaints, but every PBToolboxAI component of the application shows the busy cursor and ignores clicks until the call returns; and the component's events as well as the timers of your windows keep arriving while waiting — a synchronous sound asked from one of them is refused (-4). Keep it for short sounds — a chime, a prompt — and useof_playfor music.- A missing file is not an exception:
of_play_syncreturns-4,of_playraisesue_failed, andof_get_last_error()names the file. - Do not destroy the player from one of its events: during a synchronous call its events keep arriving, and a
destroyin one of them leaves the call resuming on a destroyed object.of_closeis enough. - A source refused at once is not an event:
-5andof_get_last_error(), right away; only the queue reports it throughue_failed, at its turn.