Update spec for changing license to MIT
[platform/upstream/alure.git] / src / streamplay.cpp
1 /*
2  * ALURE  OpenAL utility library
3  * Copyright (c) 2009 by Chris Robinson.
4  *
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:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
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.
22  */
23
24 /* Title: Automatic Playback */
25
26 #include "config.h"
27
28 #include "main.h"
29
30 #include <list>
31 #include <vector>
32
33 #ifdef HAVE_WINDOWS_H
34
35 typedef struct {
36     ALuint (*func)(ALvoid*);
37     ALvoid *ptr;
38     HANDLE thread;
39 } ThreadInfo;
40
41 static DWORD CALLBACK StarterFunc(void *ptr)
42 {
43     ThreadInfo *inf = (ThreadInfo*)ptr;
44     ALint ret;
45
46     ret = inf->func(inf->ptr);
47     ExitThread((DWORD)ret);
48
49     return (DWORD)ret;
50 }
51
52 static ThreadInfo *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr)
53 {
54     DWORD dummy;
55     ThreadInfo *inf = new ThreadInfo;
56     inf->func = func;
57     inf->ptr = ptr;
58
59     inf->thread = CreateThread(NULL, 0, StarterFunc, inf, 0, &dummy);
60     if(!inf->thread)
61     {
62         delete inf;
63         return NULL;
64     }
65
66     return inf;
67 }
68
69 static ALuint StopThread(ThreadInfo *inf)
70 {
71     WaitForSingleObject(inf->thread, INFINITE);
72     CloseHandle(inf->thread);
73     delete inf;
74
75     return 0;
76 }
77
78 #else
79
80 typedef struct {
81     ALuint (*func)(ALvoid*);
82     ALvoid *ptr;
83     pthread_t thread;
84 } ThreadInfo;
85
86 static void *StarterFunc(void *ptr)
87 {
88     ThreadInfo *inf = (ThreadInfo*)ptr;
89     void *ret = (void*)(inf->func(inf->ptr));
90     return ret;
91 }
92
93 static ThreadInfo *StartThread(ALuint (*func)(ALvoid*), ALvoid *ptr)
94 {
95     ThreadInfo *inf = new ThreadInfo;
96     inf->func = func;
97     inf->ptr = ptr;
98
99     if(pthread_create(&inf->thread, NULL, StarterFunc, inf) != 0)
100     {
101         delete inf;
102         return NULL;
103     }
104
105     return inf;
106 }
107
108 static ALuint StopThread(ThreadInfo *inf)
109 {
110     pthread_join(inf->thread, NULL);
111     delete inf;
112
113     return 0;
114 }
115
116 #endif
117
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()
125         { protect(); }
126
127         ~ProtectContext()
128         { unprotect(); }
129
130         void protect()
131         {
132                 old_ctx = (alcGetThreadContext ? alcGetThreadContext() : NULL);
133                 if(alcSetThreadContext)
134                         alcSetThreadContext(alcGetCurrentContext());
135         }
136
137         void unprotect()
138         {
139                 if(alcSetThreadContext)
140                 {
141                         if(alcSetThreadContext(old_ctx) == ALC_FALSE)
142                                 alcSetThreadContext(NULL);
143                 }
144         }
145
146 private:
147         ALCcontext *old_ctx;
148 };
149 #define PROTECT_CONTEXT() ProtectContext _ctx_prot
150 #define DO_PROTECT()      _ctx_prot.protect()
151 #define DO_UNPROTECT()    _ctx_prot.unprotect()
152
153 struct AsyncPlayEntry {
154         ALuint source;
155         alureStream *stream;
156         std::vector<ALuint> buffers;
157         ALsizei loopcount;
158         ALsizei maxloops;
159         void (*eos_callback)(void*,ALuint);
160         void *user_data;
161         bool finished;
162         bool paused;
163         ALuint stream_freq;
164         ALenum stream_format;
165         ALuint stream_align;
166         ALCcontext *ctx;
167
168         AsyncPlayEntry() : source(0), stream(NULL), loopcount(0), maxloops(0),
169                            eos_callback(NULL), user_data(NULL), finished(false),
170                            paused(false), stream_freq(0), stream_format(AL_NONE),
171                            stream_align(0), ctx(NULL)
172         { }
173         AsyncPlayEntry(const AsyncPlayEntry &rhs)
174           : source(rhs.source), stream(rhs.stream), buffers(rhs.buffers),
175             loopcount(rhs.loopcount), maxloops(rhs.maxloops),
176             eos_callback(rhs.eos_callback), user_data(rhs.user_data),
177             finished(rhs.finished), paused(rhs.paused),
178             stream_freq(rhs.stream_freq), stream_format(rhs.stream_format),
179             stream_align(rhs.stream_align), ctx(rhs.ctx)
180         { }
181
182         ALenum Update(ALint *queued)
183         {
184                 ALint processed, state;
185
186                 alGetSourcei(source, AL_SOURCE_STATE, &state);
187                 alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
188                 while(processed > 0)
189                 {
190                         ALuint buf;
191
192                         alSourceUnqueueBuffers(source, 1, &buf);
193                         processed--;
194
195                         while(!finished)
196                         {
197                                 ALuint got = stream->GetData(&stream->dataChunk[0], stream->dataChunk.size());
198                                 got -= got%stream_align;
199                                 if(got > 0)
200                                 {
201                                         alBufferData(buf, stream_format, &stream->dataChunk[0], got, stream_freq);
202                                         alSourceQueueBuffers(source, 1, &buf);
203
204                                         break;
205                                 }
206                                 if(loopcount == maxloops)
207                                 {
208                                         finished = true;
209                                         break;
210                                 }
211                                 if(maxloops != -1)
212                                         loopcount++;
213                                 finished = !stream->Rewind();
214                         }
215                 }
216
217                 alGetSourcei(source, AL_BUFFERS_QUEUED, queued);
218                 return state;
219         }
220 };
221 static std::list<AsyncPlayEntry> AsyncPlayList;
222 static ThreadInfo *PlayThreadHandle;
223
224 ALfloat CurrentInterval = 0.0f;
225
226 ALuint AsyncPlayFunc(ALvoid*)
227 {
228         EnterCriticalSection(&cs_StreamPlay);
229         while(CurrentInterval > 0.0f)
230         {
231                 alureUpdate();
232
233                 ALfloat interval = CurrentInterval;
234                 LeaveCriticalSection(&cs_StreamPlay);
235                 alureSleep(interval);
236                 EnterCriticalSection(&cs_StreamPlay);
237         }
238         LeaveCriticalSection(&cs_StreamPlay);
239         return 0;
240 }
241
242
243 void StopStream(alureStream *stream)
244 {
245         EnterCriticalSection(&cs_StreamPlay);
246
247         std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
248                                             end = AsyncPlayList.end();
249         while(i != end)
250         {
251                 if(i->stream == stream)
252                 {
253                         AsyncPlayEntry ent(*i);
254                         AsyncPlayList.erase(i);
255
256                         ALCcontext *old_ctx = (alcGetThreadContext ?
257                                                alcGetThreadContext() : NULL);
258                         if(alcSetThreadContext)
259                         {
260                                 if(alcSetThreadContext(ent.ctx) == ALC_FALSE)
261                                         goto ctx_err;
262                         }
263
264                         alSourceStop(ent.source);
265                         alSourcei(ent.source, AL_BUFFER, 0);
266                         alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
267                         alGetError();
268
269                         if(alcSetThreadContext)
270                         {
271                                 if(alcSetThreadContext(old_ctx) == ALC_FALSE)
272                                         alcSetThreadContext(NULL);
273                         }
274
275                 ctx_err:
276                         if(ent.eos_callback)
277                                 ent.eos_callback(ent.user_data, ent.source);
278                         break;
279                 }
280                 i++;
281         }
282
283         LeaveCriticalSection(&cs_StreamPlay);
284 }
285
286
287 extern "C" {
288
289 /* Function: alurePlaySourceStream
290  *
291  * Starts playing a stream, using the specified source ID. A stream can only be
292  * played if it is not already playing. You must call <alureUpdate> at regular
293  * intervals to keep the stream playing, or else the stream will underrun and
294  * cause a break in the playback until an update call can restart it. It is
295  * also important that the current context is kept for <alureUpdate> calls if
296  * ALC_EXT_thread_local_context is not supported, otherwise the method may
297  * start calling OpenAL with invalid IDs. Note that checking the state of the
298  * specified source is not a good method to determine if a stream is playing.
299  * If an underrun occurs, the source will enter a stopped state until it is
300  * automatically restarted. Instead, set a flag using the callback to indicate
301  * the stream being stopped.
302  *
303  * Parameters:
304  * source - The source ID to play the stream with. Any buffers on the source
305  *          will be unqueued. It is valid to set source properties not related
306  *          to the buffer queue or playback state (ie. you may change the
307  *          source's position, pitch, gain, etc, but you must not stop the
308  *          source or queue/unqueue buffers on it). To pause the source, call
309  *          <alurePauseSource>.
310  * stream - The stream to play. Any valid stream will work, although looping
311  *          will only work if the stream can be rewound (eg. streams made with
312  *          <alureCreateStreamFromCallback> cannot loop, but will play for as
313  *          long as the callback provides data).
314  * numBufs - The number of buffers used to queue with the OpenAL source. Each
315  *           buffer will be filled with the chunk length specified when the
316  *           stream was created. This value must be at least 2. More buffers at
317  *           a larger size will increase the time needed between updates, but
318  *           at the cost of more memory usage.
319  * loopcount - The number of times to loop the stream. When the stream reaches
320  *             the end of processing, it will be rewound to continue buffering
321  *             data. A value of -1 will cause the stream to loop indefinitely
322  *             (or until <alureStopSource> is called).
323  * eos_callback - This callback will be called when the stream reaches the end,
324  *                no more loops are pending, and the source reaches a stopped
325  *                state. It will also be called if an error occured and
326  *                playback terminated.
327  * userdata - An opaque user pointer passed to the callback.
328  *
329  * Returns:
330  * AL_FALSE on error.
331  *
332  * *Version Added*: 1.1
333  *
334  * See Also:
335  * <alureStopSource>, <alurePauseSource>, <alureUpdate>
336  */
337 ALURE_API ALboolean ALURE_APIENTRY alurePlaySourceStream(ALuint source,
338     alureStream *stream, ALsizei numBufs, ALsizei loopcount,
339     void (*eos_callback)(void *userdata, ALuint source), void *userdata)
340 {
341         PROTECT_CONTEXT();
342         ALCcontext *current_ctx = alcGetCurrentContext();
343
344         if(alGetError() != AL_NO_ERROR)
345         {
346                 SetError("Existing OpenAL error");
347                 return AL_FALSE;
348         }
349
350         if(!alureStream::Verify(stream))
351         {
352                 SetError("Invalid stream pointer");
353                 return AL_FALSE;
354         }
355
356         if(numBufs < 2)
357         {
358                 SetError("Invalid buffer count");
359                 return AL_FALSE;
360         }
361
362         if(!alIsSource(source))
363         {
364                 SetError("Invalid source ID");
365                 return AL_FALSE;
366         }
367
368         EnterCriticalSection(&cs_StreamPlay);
369
370         std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
371                                             end = AsyncPlayList.end();
372         while(i != end)
373         {
374                 if(i->stream == stream)
375                 {
376                         SetError("Stream is already playing");
377                         LeaveCriticalSection(&cs_StreamPlay);
378                         return AL_FALSE;
379                 }
380                 if(i->source == source && i->ctx == current_ctx)
381                 {
382                         SetError("Source is already playing");
383                         LeaveCriticalSection(&cs_StreamPlay);
384                         return AL_FALSE;
385                 }
386                 i++;
387         }
388
389         AsyncPlayEntry ent;
390         ent.stream = stream;
391         ent.source = source;
392         ent.maxloops = loopcount;
393         ent.eos_callback = eos_callback;
394         ent.user_data = userdata;
395         ent.ctx = current_ctx;
396
397         ent.buffers.resize(numBufs);
398         alGenBuffers(ent.buffers.size(), &ent.buffers[0]);
399         if(alGetError() != AL_NO_ERROR)
400         {
401                 LeaveCriticalSection(&cs_StreamPlay);
402                 SetError("Error generating buffers");
403                 return AL_FALSE;
404         }
405
406         numBufs = 0;
407         if(ent.stream->GetFormat(&ent.stream_format, &ent.stream_freq, &ent.stream_align))
408         {
409                 for(size_t i = 0;i < ent.buffers.size();i++)
410                 {
411                         ALuint got = ent.stream->GetData(&ent.stream->dataChunk[0],
412                                                          ent.stream->dataChunk.size());
413                         got -= got%ent.stream_align;
414                         if(got <= 0)
415                         {
416                                 if(ent.loopcount == ent.maxloops || i == 0)
417                                         ent.finished = true;
418                                 else
419                                 {
420                                         if(ent.maxloops != -1)
421                                                 ent.loopcount++;
422                                         ent.finished = !ent.stream->Rewind();
423                                 }
424                                 if(ent.finished)
425                                         break;
426                                 i--;
427                                 continue;
428                         }
429                         ALuint buf = ent.buffers[i];
430                         alBufferData(buf, ent.stream_format, &ent.stream->dataChunk[0], got, ent.stream_freq);
431                         numBufs++;
432                 }
433         }
434         if(numBufs == 0)
435         {
436                 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
437                 alGetError();
438                 LeaveCriticalSection(&cs_StreamPlay);
439                 SetError("Error buffering from stream");
440                 return AL_FALSE;
441         }
442
443         if((alSourcei(source, AL_LOOPING, AL_FALSE),
444             alSourcei(source, AL_BUFFER, 0),alGetError()) != AL_NO_ERROR ||
445            (alSourceQueueBuffers(source, numBufs, &ent.buffers[0]),
446             alSourcePlay(source),alGetError()) != AL_NO_ERROR)
447         {
448                 alSourcei(source, AL_BUFFER, 0);
449                 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
450                 alGetError();
451                 LeaveCriticalSection(&cs_StreamPlay);
452                 SetError("Error starting source");
453                 return AL_FALSE;
454         }
455
456         AsyncPlayList.push_front(ent);
457
458         LeaveCriticalSection(&cs_StreamPlay);
459
460         return AL_TRUE;
461 }
462
463 /* Function: alurePlaySource
464  *
465  * Plays the specified source ID and watches for it to stop. When the source
466  * enters an AL_STOPPED or AL_INITIAL state, the specified callback will be
467  * called by <alureUpdate> to alert the application. If
468  * ALC_EXT_thread_local_context is not supported, the current context must not
469  * be changed while the source is being watched (before the callback is called
470  * or <alureStopSource> is called). It also must not be deleted while being
471  * watched.
472  *
473  * Parameters:
474  * source - The source ID to play. As with <alurePlaySourceStream>, it is valid
475  *          to set source properties not related to the playback state (ie. you
476  *          may change a source's position, pitch, gain, etc). Pausing a source
477  *          and restarting a paused source is allowed, and the callback will
478  *          still be called when the source reaches an AL_STOPPED or AL_INITIAL
479  *          state.
480  * callback - The callback to be called when the source stops.
481  * userdata - An opaque user pointer passed to the callback.
482  *
483  * Returns:
484  * AL_FALSE on error.
485  *
486  * *Version Added*: 1.1
487  *
488  * See Also:
489  * <alureStopSource>, <alureUpdate>
490  */
491 ALURE_API ALboolean ALURE_APIENTRY alurePlaySource(ALuint source,
492     void (*callback)(void *userdata, ALuint source), void *userdata)
493 {
494         PROTECT_CONTEXT();
495         ALCcontext *current_ctx = alcGetCurrentContext();
496
497         if(alGetError() != AL_NO_ERROR)
498         {
499                 SetError("Existing OpenAL error");
500                 return AL_FALSE;
501         }
502
503         EnterCriticalSection(&cs_StreamPlay);
504
505         std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
506                                             end = AsyncPlayList.end();
507         while(i != end)
508         {
509                 if(i->source == source && i->ctx == current_ctx)
510                 {
511                         SetError("Source is already playing");
512                         LeaveCriticalSection(&cs_StreamPlay);
513                         return AL_FALSE;
514                 }
515                 i++;
516         }
517
518         if((alSourcePlay(source),alGetError()) != AL_NO_ERROR)
519         {
520                 LeaveCriticalSection(&cs_StreamPlay);
521                 SetError("Error starting source");
522                 return AL_FALSE;
523         }
524
525         if(callback != NULL)
526         {
527                 AsyncPlayEntry ent;
528                 ent.source = source;
529                 ent.eos_callback = callback;
530                 ent.user_data = userdata;
531                 ent.ctx = current_ctx;
532                 AsyncPlayList.push_front(ent);
533         }
534
535         LeaveCriticalSection(&cs_StreamPlay);
536
537         return AL_TRUE;
538 }
539
540 /* Function: alureStopSource
541  *
542  * Stops the specified source ID, and any associated stream. The previously
543  * specified callback will be invoked if 'run_callback' is not AL_FALSE.
544  * Sources that were not started with <alurePlaySourceStream> or
545  * <alurePlaySource> will still be stopped, but will not have any callback
546  * called for them.
547  *
548  * Returns:
549  * AL_FALSE on error.
550  *
551  * *Version Added*: 1.1
552  *
553  * See Also:
554  * <alurePlaySourceStream>, <alurePlaySource>
555  */
556 ALURE_API ALboolean ALURE_APIENTRY alureStopSource(ALuint source, ALboolean run_callback)
557 {
558         PROTECT_CONTEXT();
559         ALCcontext *current_ctx = alcGetCurrentContext();
560
561         if(alGetError() != AL_NO_ERROR)
562         {
563                 SetError("Existing OpenAL error");
564                 return AL_FALSE;
565         }
566
567         EnterCriticalSection(&cs_StreamPlay);
568
569         if((alSourceStop(source),alGetError()) != AL_NO_ERROR)
570         {
571                 LeaveCriticalSection(&cs_StreamPlay);
572                 SetError("Error stopping source");
573                 return AL_FALSE;
574         }
575
576         std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
577                                             end = AsyncPlayList.end();
578         while(i != end)
579         {
580                 if(i->source == source && i->ctx == current_ctx)
581                 {
582                         AsyncPlayEntry ent(*i);
583                         AsyncPlayList.erase(i);
584
585                         if(ent.buffers.size() > 0)
586                         {
587                                 alSourcei(ent.source, AL_BUFFER, 0);
588                                 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
589                                 alGetError();
590                         }
591
592                         if(run_callback && ent.eos_callback)
593                         {
594                                 DO_UNPROTECT();
595                                 ent.eos_callback(ent.user_data, ent.source);
596                                 DO_PROTECT();
597                         }
598                         break;
599                 }
600                 i++;
601         }
602
603         LeaveCriticalSection(&cs_StreamPlay);
604
605         return AL_TRUE;
606 }
607
608 /* Function: alurePauseSource
609  *
610  * Pauses the specified source ID, and any associated stream. This is needed to
611  * avoid potential race conditions with sources that are playing a stream.
612  *
613  * Note that it is possible for the specified source to become stopped, and any
614  * associated stream to finish, before this function is called, causing the
615  * callback to be delayed until after the function returns and <alureUpdate>
616  * detects the stopped source.
617  *
618  * Returns:
619  * AL_FALSE on error.
620  *
621  * *Version Added*: 1.1
622  *
623  * See Also:
624  * <alureResumeSource>, <alurePlaySourceStream>, <alurePlaySource>
625  */
626 ALURE_API ALboolean ALURE_APIENTRY alurePauseSource(ALuint source)
627 {
628         PROTECT_CONTEXT();
629         ALCcontext *current_ctx = alcGetCurrentContext();
630
631         if(alGetError() != AL_NO_ERROR)
632         {
633                 SetError("Existing OpenAL error");
634                 return AL_FALSE;
635         }
636
637         EnterCriticalSection(&cs_StreamPlay);
638
639         if((alSourcePause(source),alGetError()) != AL_NO_ERROR)
640         {
641                 SetError("Error pausing source");
642                 LeaveCriticalSection(&cs_StreamPlay);
643                 return AL_FALSE;
644         }
645
646         std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
647                                             end = AsyncPlayList.end();
648         while(i != end)
649         {
650                 if(i->source == source && i->ctx == current_ctx)
651                 {
652                         i->paused = true;
653                         break;
654                 }
655                 i++;
656         }
657
658         LeaveCriticalSection(&cs_StreamPlay);
659
660         return AL_TRUE;
661 }
662
663 /* Function: alureResumeSource
664  *
665  * Resumes the specified source ID after being paused.
666  *
667  * Returns:
668  * AL_FALSE on error.
669  *
670  * *Version Added*: 1.1
671  *
672  * See Also:
673  * <alurePauseSource>
674  */
675 ALURE_API ALboolean ALURE_APIENTRY alureResumeSource(ALuint source)
676 {
677         PROTECT_CONTEXT();
678         ALCcontext *current_ctx = alcGetCurrentContext();
679
680         if(alGetError() != AL_NO_ERROR)
681         {
682                 SetError("Existing OpenAL error");
683                 return AL_FALSE;
684         }
685
686         EnterCriticalSection(&cs_StreamPlay);
687
688         if((alSourcePlay(source),alGetError()) != AL_NO_ERROR)
689         {
690                 SetError("Error playing source");
691                 LeaveCriticalSection(&cs_StreamPlay);
692                 return AL_FALSE;
693         }
694
695         std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
696                                             end = AsyncPlayList.end();
697         while(i != end)
698         {
699                 if(i->source == source && i->ctx == current_ctx)
700                 {
701                         i->paused = false;
702                         break;
703                 }
704                 i++;
705         }
706
707         LeaveCriticalSection(&cs_StreamPlay);
708
709         return AL_TRUE;
710 }
711
712 /* Function: alureUpdate
713  *
714  * Updates the running list of streams, and checks for stopped sources. This
715  * makes sure that sources played with <alurePlaySourceStream> are kept fed
716  * from their associated stream, and sources played with <alurePlaySource> are
717  * still playing. It will call their callbacks as needed.
718  *
719  * *Version Added*: 1.1
720  *
721  * See Also:
722  * <alurePlaySourceStream>, <alurePlaySource>
723  */
724 ALURE_API void ALURE_APIENTRY alureUpdate(void)
725 {
726         PROTECT_CONTEXT();
727
728         EnterCriticalSection(&cs_StreamPlay);
729 restart:
730         std::list<AsyncPlayEntry>::iterator i = AsyncPlayList.begin(),
731                                             end = AsyncPlayList.end();
732         for(;i != end;i++)
733         {
734                 if(alcSetThreadContext)
735                 {
736                         if(alcSetThreadContext(i->ctx) == ALC_FALSE)
737                         {
738                                 AsyncPlayEntry ent(*i);
739                                 AsyncPlayList.erase(i);
740                                 if(ent.eos_callback)
741                                 {
742                                         DO_UNPROTECT();
743                                         ent.eos_callback(ent.user_data, ent.source);
744                                         DO_PROTECT();
745                                 }
746                                 goto restart;
747                         }
748                 }
749
750                 if(i->stream == NULL)
751                 {
752                         ALint state;
753                         alGetSourcei(i->source, AL_SOURCE_STATE, &state);
754                         if(state == AL_STOPPED || state == AL_INITIAL)
755                         {
756                                 AsyncPlayEntry ent(*i);
757                                 AsyncPlayList.erase(i);
758                                 DO_UNPROTECT();
759                                 ent.eos_callback(ent.user_data, ent.source);
760                                 DO_PROTECT();
761                                 goto restart;
762                         }
763                         continue;
764                 }
765
766                 ALint queued;
767                 if(i->Update(&queued) != AL_PLAYING)
768                 {
769                         if(queued == 0)
770                         {
771                                 AsyncPlayEntry ent(*i);
772                                 AsyncPlayList.erase(i);
773
774                                 alSourcei(ent.source, AL_BUFFER, 0);
775                                 alDeleteBuffers(ent.buffers.size(), &ent.buffers[0]);
776                                 if(ent.eos_callback)
777                                 {
778                                         DO_UNPROTECT();
779                                         ent.eos_callback(ent.user_data, ent.source);
780                                         DO_PROTECT();
781                                 }
782                                 goto restart;
783                         }
784                         if(!i->paused)
785                                 alSourcePlay(i->source);
786                 }
787         }
788         LeaveCriticalSection(&cs_StreamPlay);
789 }
790
791 /* Function: alureUpdateInterval
792  *
793  * Sets up a timer or thread to automatically call <alureUpdate> at the given
794  * interval, in seconds. If the timer or thread is already running, the update
795  * interval will be modified. A 0 or negative interval will stop <alureUpdate>
796  * from being called.
797  *
798  * Returns:
799  * AL_FALSE on error.
800  *
801  * *Version Added*: 1.1
802  *
803  * See Also:
804  * <alureUpdate>
805  */
806 ALURE_API ALboolean ALURE_APIENTRY alureUpdateInterval(ALfloat interval)
807 {
808         EnterCriticalSection(&cs_StreamPlay);
809         if(interval <= 0.0f)
810         {
811                 CurrentInterval = 0.0f;
812                 if(PlayThreadHandle)
813                 {
814                         ThreadInfo *threadinf = PlayThreadHandle;
815                         PlayThreadHandle = NULL;
816                         LeaveCriticalSection(&cs_StreamPlay);
817                         StopThread(threadinf);
818                         EnterCriticalSection(&cs_StreamPlay);
819                 }
820         }
821         else if(interval > 0.0f)
822         {
823                 if(!PlayThreadHandle)
824                         PlayThreadHandle = StartThread(AsyncPlayFunc, NULL);
825                 if(!PlayThreadHandle)
826                 {
827                         SetError("Error starting async thread");
828                         LeaveCriticalSection(&cs_StreamPlay);
829                         return AL_FALSE;
830                 }
831                 CurrentInterval = interval;
832         }
833         LeaveCriticalSection(&cs_StreamPlay);
834
835         return AL_TRUE;
836 }
837
838 } // extern "C"