Set the right context when a stream is being stopped
[alure.git] / src / streamplay.cpp
blobdd932b849a8c6f52198700b53efad3658a648c7b
1 /*
2 * ALURE OpenAL utility library
3 * Copyright (c) 2009 by Chris Robinson.
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
24 /* Title: Automatic Playback */
26 #include "config.h"
28 #include "main.h"
30 #include <list>
31 #include <vector>
33 #ifdef HAVE_WINDOWS_H
35 typedef struct {
36 ALuint (*func)(ALvoid*);
37 ALvoid *ptr;
38 HANDLE thread;
39 } ThreadInfo;
41 static DWORD CALLBACK StarterFunc(void *ptr)
43 ThreadInfo *inf = (ThreadInfo*)ptr;
44 ALint ret;
46 ret = inf->func(inf->ptr);
47 ExitThread((DWORD)ret);
49 return (DWORD)ret;
52 static ThreadInfo *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr)
54 DWORD dummy;
55 ThreadInfo *inf = new ThreadInfo;
56 inf->func = func;
57 inf->ptr = ptr;
59 inf->thread = CreateThread(NULL, 0, StarterFunc, inf, 0, &dummy);
60 if(!inf->thread)
62 delete inf;
63 return NULL;
66 return inf;
69 static ALuint StopThread(ThreadInfo *inf)
71 WaitForSingleObject(inf->thread, INFINITE);
72 CloseHandle(inf->thread);
73 delete inf;
75 return 0;
78 #else
80 typedef struct {
81 ALuint (*func)(ALvoid*);
82 ALvoid *ptr;
83 pthread_t thread;
84 } ThreadInfo;
86 static void *StarterFunc(void *ptr)
88 ThreadInfo *inf = (ThreadInfo*)ptr;
89 void *ret = (void*)(inf->func(inf->ptr));
90 return ret;
93 static ThreadInfo *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr)
95 ThreadInfo *inf = new ThreadInfo;
96 inf->func = func;
97 inf->ptr = ptr;
99 if(pthread_create(&inf->thread, NULL, StarterFunc, inf) != 0)
101 delete inf;
102 return NULL;
105 return inf;
108 static ALuint StopThread(ThreadInfo *inf)
110 pthread_join(inf->thread, NULL);
111 delete inf;
113 return 0;
116 #endif
118 // This object is used to make sure the current context isn't switched out on
119 // us by another thread, by setting the current thread context to the current
120 // context. The old thread context is then restored when the object goes out
121 // of scope.
122 // This obviously only works when ALC_EXT_thread_local_context is supported
123 struct ProtectContext {
124 ProtectContext()
126 old_ctx = (alcGetThreadContext ? alcGetThreadContext() : NULL);
127 if(alcSetThreadContext)
128 alcSetThreadContext(alcGetCurrentContext());
130 ~ProtectContext()
132 if(alcSetThreadContext)
134 if(alcSetThreadContext(old_ctx) == ALC_FALSE)
135 alcSetThreadContext(NULL);
139 private:
140 ALCcontext *old_ctx;
142 #define PROTECT_CONTEXT() ProtectContext _ctx_prot
144 struct AsyncPlayEntry {
145 ALuint source;
146 alureStream *stream;
147 std::vector<ALuint> buffers;
148 ALsizei loopcount;
149 ALsizei maxloops;
150 void (*eos_callback)(void*,ALuint);
151 void *user_data;
152 bool finished;
153 bool paused;
154 ALuint stream_freq;
155 ALenum stream_format;
156 ALuint stream_align;
157 ALCcontext *ctx;
159 AsyncPlayEntry() : source(0), stream(NULL), loopcount(0), maxloops(0),
160 eos_callback(NULL), user_data(NULL), finished(false),
161 paused(false), stream_freq(0), stream_format(AL_NONE),
162 stream_align(0), ctx(NULL)
164 AsyncPlayEntry(const AsyncPlayEntry &rhs)
165 : source(rhs.source), stream(rhs.stream), buffers(rhs.buffers),
166 loopcount(rhs.loopcount), maxloops(rhs.maxloops),
167 eos_callback(rhs.eos_callback), user_data(rhs.user_data),
168 finished(rhs.finished), paused(rhs.paused),
169 stream_freq(rhs.stream_freq), stream_format(rhs.stream_format),
170 stream_align(rhs.stream_align), ctx(rhs.ctx)
173 ALenum Update(ALint *queued)
175 ALint processed, state;
177 alGetSourcei(source, AL_SOURCE_STATE, &state);
178 alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
179 while(processed > 0)
181 ALuint buf;
183 alSourceUnqueueBuffers(source, 1, &buf);
184 processed--;
186 while(!finished)
188 ALuint got = stream->GetData(&stream->dataChunk[0], stream->dataChunk.size());
189 got -= got%stream_align;
190 if(got > 0)
192 alBufferData(buf, stream_format, &stream->dataChunk[0], got, stream_freq);
193 alSourceQueueBuffers(source, 1, &buf);
195 break;
197 if(loopcount == maxloops)
199 finished = true;
200 break;
202 if(maxloops != -1)
203 loopcount++;
204 finished = !stream->Rewind();
208 alGetSourcei(source, AL_BUFFERS_QUEUED, queued);
209 return state;
212 static std::list<AsyncPlayEntry> AsyncPlayList;
213 static ThreadInfo *PlayThreadHandle;
215 ALfloat CurrentInterval = 0.0f;
217 ALuint AsyncPlayFunc(ALvoid*)
219 EnterCriticalSection(&cs_StreamPlay);
220 while(CurrentInterval > 0.0f)
222 alureUpdate();
224 ALfloat interval = CurrentInterval;
225 LeaveCriticalSection(&cs_StreamPlay);
226 alureSleep(interval);
227 EnterCriticalSection(&cs_StreamPlay);
229 LeaveCriticalSection(&cs_StreamPlay);
230 return 0;
234 void StopStream(alureStream *stream)
236 EnterCriticalSection(&cs_StreamPlay);
238 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
239 end = AsyncPlayList.end();
240 while(i != end)
242 if(i->stream == stream)
244 ALCcontext *old_ctx = (alcGetThreadContext ?
245 alcGetThreadContext() : NULL);
246 if(alcSetThreadContext) alcSetThreadContext(i->ctx);
248 AsyncPlayEntry ent(*i);
249 AsyncPlayList.erase(i);
251 alSourceStop(ent.source);
252 alSourcei(ent.source, AL_BUFFER, 0);
253 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
254 alGetError();
256 if(ent.eos_callback)
257 ent.eos_callback(ent.user_data, ent.source);
259 if(alcSetThreadContext)
261 if(alcSetThreadContext(old_ctx) == ALC_FALSE)
262 alcSetThreadContext(NULL);
264 break;
266 i++;
269 LeaveCriticalSection(&cs_StreamPlay);
273 extern "C" {
275 /* Function: alurePlaySourceStream
277 * Starts playing a stream, using the specified source ID. A stream can only be
278 * played if it is not already playing. You must call <alureUpdate> at regular
279 * intervals to keep the stream playing, or else the stream will underrun and
280 * cause a break in the playback until an update call can restart it. It is
281 * also important that the current context is kept for <alureUpdate> calls if
282 * ALC_EXT_thread_local_context is not supported, otherwise the method may
283 * start calling OpenAL with invalid IDs. Note that checking the state of the
284 * specified source is not a good method to determine if a stream is playing.
285 * If an underrun occurs, the source will enter a stopped state until it is
286 * automatically restarted. Instead, set a flag using the callback to indicate
287 * the stream being stopped.
289 * Parameters:
290 * source - The source ID to play the stream with. Any buffers on the source
291 * will be unqueued. It is valid to set source properties not related
292 * to the buffer queue or playback state (ie. you may change the
293 * source's position, pitch, gain, etc, but you must not stop the
294 * source or queue/unqueue buffers on it). To pause the source, call
295 * <alurePauseSource>.
296 * stream - The stream to play. Any valid stream will work, although looping
297 * will only work if the stream can be rewound (eg. streams made with
298 * <alureCreateStreamFromCallback> cannot loop, but will play for as
299 * long as the callback provides data).
300 * numBufs - The number of buffers used to queue with the OpenAL source. Each
301 * buffer will be filled with the chunk length specified when the
302 * stream was created. This value must be at least 2. More buffers at
303 * a larger size will decrease the time needed between updates, but
304 * at the cost of more memory usage.
305 * loopcount - The number of times to loop the stream. When the stream reaches
306 * the end of processing, it will be rewound to continue buffering
307 * data. A value of -1 will cause the stream to loop indefinitely
308 * (or until <alureStopSource> is called).
309 * eos_callback - This callback will be called when the stream reaches the end,
310 * no more loops are pending, and the source reaches a stopped
311 * state. It will also be called if an error occured and
312 * playback terminated.
313 * userdata - An opaque user pointer passed to the callback.
315 * Returns:
316 * AL_FALSE on error.
318 * *Version Added*: 1.1
320 * See Also:
321 * <alureStopSource>, <alurePauseSource>, <alureUpdate>
323 ALURE_API ALboolean ALURE_APIENTRY alurePlaySourceStream(ALuint source,
324 alureStream *stream, ALsizei numBufs, ALsizei loopcount,
325 void (*eos_callback)(void *userdata, ALuint source), void *userdata)
327 PROTECT_CONTEXT();
328 ALCcontext *current_ctx = alcGetCurrentContext();
330 if(alGetError() != AL_NO_ERROR)
332 SetError("Existing OpenAL error");
333 return AL_FALSE;
336 if(!alureStream::Verify(stream))
338 SetError("Invalid stream pointer");
339 return AL_FALSE;
342 if(numBufs < 2)
344 SetError("Invalid buffer count");
345 return AL_FALSE;
348 if(!alIsSource(source))
350 SetError("Invalid source ID");
351 return AL_FALSE;
354 EnterCriticalSection(&cs_StreamPlay);
356 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
357 end = AsyncPlayList.end();
358 while(i != end)
360 if(i->stream == stream)
362 SetError("Stream is already playing");
363 LeaveCriticalSection(&cs_StreamPlay);
364 return AL_FALSE;
366 if(i->source == source && i->ctx == current_ctx)
368 SetError("Source is already playing");
369 LeaveCriticalSection(&cs_StreamPlay);
370 return AL_FALSE;
372 i++;
375 AsyncPlayEntry ent;
376 ent.stream = stream;
377 ent.source = source;
378 ent.maxloops = loopcount;
379 ent.eos_callback = eos_callback;
380 ent.user_data = userdata;
381 ent.ctx = current_ctx;
383 ent.buffers.resize(numBufs);
384 alGenBuffers(ent.buffers.size(), &ent.buffers[0]);
385 if(alGetError() != AL_NO_ERROR)
387 LeaveCriticalSection(&cs_StreamPlay);
388 SetError("Error generating buffers");
389 return AL_FALSE;
392 numBufs = 0;
393 if(ent.stream->GetFormat(&ent.stream_format, &ent.stream_freq, &ent.stream_align))
395 for(size_t i = 0;i < ent.buffers.size();i++)
397 ALuint got = ent.stream->GetData(&ent.stream->dataChunk[0],
398 ent.stream->dataChunk.size());
399 got -= got%ent.stream_align;
400 if(got <= 0)
402 if(ent.loopcount == ent.maxloops || i == 0)
403 ent.finished = true;
404 else
406 if(ent.maxloops != -1)
407 ent.loopcount++;
408 ent.finished = !ent.stream->Rewind();
410 if(ent.finished)
411 break;
412 i--;
413 continue;
415 ALuint buf = ent.buffers[i];
416 alBufferData(buf, ent.stream_format, &ent.stream->dataChunk[0], got, ent.stream_freq);
417 numBufs++;
420 if(numBufs == 0)
422 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
423 alGetError();
424 LeaveCriticalSection(&cs_StreamPlay);
425 SetError("Error buffering from stream");
426 return AL_FALSE;
429 if((alSourcei(source, AL_LOOPING, AL_FALSE),
430 alSourcei(source, AL_BUFFER, 0),alGetError()) != AL_NO_ERROR ||
431 (alSourceQueueBuffers(source, numBufs, &ent.buffers[0]),
432 alSourcePlay(source),alGetError()) != AL_NO_ERROR)
434 alSourcei(source, AL_BUFFER, 0);
435 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
436 alGetError();
437 LeaveCriticalSection(&cs_StreamPlay);
438 SetError("Error starting source");
439 return AL_FALSE;
442 AsyncPlayList.push_front(ent);
444 LeaveCriticalSection(&cs_StreamPlay);
446 return AL_TRUE;
449 /* Function: alurePlaySource
451 * Plays the specified source ID and watches for it to stop. When a source
452 * enters an AL_STOPPED state, the specified callback will be called by
453 * <alureUpdate> to alert the application. As with <alurePlaySourceStream>, if
454 * ALC_EXT_thread_local_context is not supported, the current context must not
455 * be changed while the source is being watched (before the callback is called
456 * or <alureStopSource> is called). It also must not be deleted while being
457 * watched.
459 * Parameters:
460 * source - The source ID to play. As with <alurePlaySourceStream>, it is valid
461 * to set source properties not related to the playback state (ie. you
462 * may change a source's position, pitch, gain, etc). Pausing a source
463 * and restarting a paused source is allowed, and the callback will
464 * still be invoked when the source naturally reaches an AL_STOPPED
465 * state.
466 * callback - The callback to be called when the source stops.
467 * userdata - An opaque user pointer passed to the callback.
469 * Returns:
470 * AL_FALSE on error.
472 * *Version Added*: 1.1
474 * See Also:
475 * <alureStopSource>, <alureUpdate>
477 ALURE_API ALboolean ALURE_APIENTRY alurePlaySource(ALuint source,
478 void (*callback)(void *userdata, ALuint source), void *userdata)
480 PROTECT_CONTEXT();
481 ALCcontext *current_ctx = alcGetCurrentContext();
483 if(alGetError() != AL_NO_ERROR)
485 SetError("Existing OpenAL error");
486 return AL_FALSE;
489 EnterCriticalSection(&cs_StreamPlay);
491 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
492 end = AsyncPlayList.end();
493 while(i != end)
495 if(i->source == source && i->ctx == current_ctx)
497 SetError("Source is already playing");
498 LeaveCriticalSection(&cs_StreamPlay);
499 return AL_FALSE;
501 i++;
504 if((alSourcePlay(source),alGetError()) != AL_NO_ERROR)
506 LeaveCriticalSection(&cs_StreamPlay);
507 SetError("Error starting source");
508 return AL_FALSE;
511 if(callback != NULL)
513 AsyncPlayEntry ent;
514 ent.source = source;
515 ent.eos_callback = callback;
516 ent.user_data = userdata;
517 ent.ctx = current_ctx;
518 AsyncPlayList.push_front(ent);
521 LeaveCriticalSection(&cs_StreamPlay);
523 return AL_TRUE;
526 /* Function: alureStopSource
528 * Stops the specified source ID, and any associated stream. The previously
529 * specified callback will be invoked if 'run_callback' is not AL_FALSE.
530 * Sources that were not started with <alurePlaySourceStream> or
531 * <alurePlaySource> will still be stopped, but will not have any callback
532 * called for them.
534 * Returns:
535 * AL_FALSE on error.
537 * *Version Added*: 1.1
539 * See Also:
540 * <alurePlaySourceStream>, <alurePlaySource>
542 ALURE_API ALboolean ALURE_APIENTRY alureStopSource(ALuint source, ALboolean run_callback)
544 PROTECT_CONTEXT();
545 ALCcontext *current_ctx = alcGetCurrentContext();
547 if(alGetError() != AL_NO_ERROR)
549 SetError("Existing OpenAL error");
550 return AL_FALSE;
553 EnterCriticalSection(&cs_StreamPlay);
555 if((alSourceStop(source),alGetError()) != AL_NO_ERROR)
557 LeaveCriticalSection(&cs_StreamPlay);
558 SetError("Error stopping source");
559 return AL_FALSE;
562 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
563 end = AsyncPlayList.end();
564 while(i != end)
566 if(i->source == source && i->ctx == current_ctx)
568 AsyncPlayEntry ent(*i);
569 AsyncPlayList.erase(i);
571 if(ent.buffers.size() > 0)
573 alSourcei(ent.source, AL_BUFFER, 0);
574 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
575 alGetError();
578 if(run_callback && ent.eos_callback)
579 ent.eos_callback(ent.user_data, ent.source);
580 break;
582 i++;
585 LeaveCriticalSection(&cs_StreamPlay);
587 return AL_TRUE;
590 /* Function: alurePauseSource
592 * Pauses the specified source ID, and any associated stream. This is needed to
593 * avoid potential race conditions with sources that are playing a stream.
595 * Note that it is possible for the specified source to become stopped, and any
596 * associated stream to finish, before this function is called, causing the
597 * callback to be delayed until after the function returns and <alureUpdate>
598 * detects the stopped source.
600 * Returns:
601 * AL_FALSE on error.
603 * *Version Added*: 1.1
605 * See Also:
606 * <alureResumeSource>, <alurePlaySourceStream>, <alurePlaySource>
608 ALURE_API ALboolean ALURE_APIENTRY alurePauseSource(ALuint source)
610 PROTECT_CONTEXT();
611 ALCcontext *current_ctx = alcGetCurrentContext();
613 if(alGetError() != AL_NO_ERROR)
615 SetError("Existing OpenAL error");
616 return AL_FALSE;
619 EnterCriticalSection(&cs_StreamPlay);
621 if((alSourcePause(source),alGetError()) != AL_NO_ERROR)
623 SetError("Error pausing source");
624 LeaveCriticalSection(&cs_StreamPlay);
625 return AL_FALSE;
628 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
629 end = AsyncPlayList.end();
630 while(i != end)
632 if(i->source == source && i->ctx == current_ctx)
634 i->paused = true;
635 break;
637 i++;
640 LeaveCriticalSection(&cs_StreamPlay);
642 return AL_TRUE;
645 /* Function: alureResumeSource
647 * Resumes the specified source ID after being paused.
649 * Returns:
650 * AL_FALSE on error.
652 * *Version Added*: 1.1
654 * See Also:
655 * <alurePauseSource>
657 ALURE_API ALboolean ALURE_APIENTRY alureResumeSource(ALuint source)
659 PROTECT_CONTEXT();
660 ALCcontext *current_ctx = alcGetCurrentContext();
662 if(alGetError() != AL_NO_ERROR)
664 SetError("Existing OpenAL error");
665 return AL_FALSE;
668 EnterCriticalSection(&cs_StreamPlay);
670 if((alSourcePlay(source),alGetError()) != AL_NO_ERROR)
672 SetError("Error playing source");
673 LeaveCriticalSection(&cs_StreamPlay);
674 return AL_FALSE;
677 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
678 end = AsyncPlayList.end();
679 while(i != end)
681 if(i->source == source && i->ctx == current_ctx)
683 i->paused = false;
684 break;
686 i++;
689 LeaveCriticalSection(&cs_StreamPlay);
691 return AL_TRUE;
694 /* Function: alureUpdate
696 * Updates the running list of streams, and checks for stopped sources. This
697 * makes sure that sources played with <alurePlaySourceStream> are kept fed
698 * from their associated stream, and sources played with <alurePlaySource> are
699 * still playing. It will call their callbacks as needed.
701 * *Version Added*: 1.1
703 * See Also:
704 * <alurePlaySourceStream>, <alurePlaySource>
706 ALURE_API void ALURE_APIENTRY alureUpdate(void)
708 PROTECT_CONTEXT();
710 EnterCriticalSection(&cs_StreamPlay);
711 restart:
712 std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
713 end = AsyncPlayList.end();
714 for(;i != end;i++)
716 if(alcSetThreadContext)
718 if(alcSetThreadContext(i->ctx) == ALC_FALSE)
720 AsyncPlayEntry ent(*i);
721 AsyncPlayList.erase(i);
722 if(ent.eos_callback)
723 ent.eos_callback(ent.user_data, ent.source);
724 goto restart;
728 if(i->stream == NULL)
730 ALint state;
731 alGetSourcei(i->source, AL_SOURCE_STATE, &state);
732 if(state != AL_PLAYING && state != AL_PAUSED)
734 AsyncPlayEntry ent(*i);
735 AsyncPlayList.erase(i);
736 ent.eos_callback(ent.user_data, ent.source);
737 goto restart;
739 continue;
742 ALint queued;
743 if(i->Update(&queued) != AL_PLAYING)
745 if(queued == 0)
747 AsyncPlayEntry ent(*i);
748 AsyncPlayList.erase(i);
750 alSourcei(ent.source, AL_BUFFER, 0);
751 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
752 if(ent.eos_callback)
753 ent.eos_callback(ent.user_data, ent.source);
754 goto restart;
756 if(!i->paused)
757 alSourcePlay(i->source);
760 LeaveCriticalSection(&cs_StreamPlay);
763 /* Function: alureUpdateInterval
765 * Sets up a timer or thread to automatically call <alureUpdate> at the given
766 * interval, in seconds. If the timer or thread is already running, the update
767 * interval will be modified. A 0 or negative interval will stop <alureUpdate>
768 * from being called.
770 * Returns:
771 * AL_FALSE on error.
773 * *Version Added*: 1.1
775 * See Also:
776 * <alureUpdate>
778 ALURE_API ALboolean ALURE_APIENTRY alureUpdateInterval(ALfloat interval)
780 EnterCriticalSection(&cs_StreamPlay);
781 if(interval <= 0.0f)
783 CurrentInterval = 0.0f;
784 if(PlayThreadHandle)
786 ThreadInfo *threadinf = PlayThreadHandle;
787 PlayThreadHandle = NULL;
788 LeaveCriticalSection(&cs_StreamPlay);
789 StopThread(threadinf);
790 EnterCriticalSection(&cs_StreamPlay);
793 else
795 if(!PlayThreadHandle)
796 PlayThreadHandle = StartThread(AsyncPlayFunc, NULL);
797 if(!PlayThreadHandle)
799 SetError("Error starting async thread");
800 LeaveCriticalSection(&cs_StreamPlay);
801 return AL_FALSE;
803 CurrentInterval = interval;
805 LeaveCriticalSection(&cs_StreamPlay);
807 return AL_TRUE;
810 } // extern "C"