|
1 /* This file is part of the KDE project. |
|
2 |
|
3 Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 |
|
5 This library is free software: you can redistribute it and/or modify |
|
6 it under the terms of the GNU Lesser General Public License as published by |
|
7 the Free Software Foundation, either version 2.1 or 3 of the License. |
|
8 |
|
9 This library is distributed in the hope that it will be useful, |
|
10 but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 GNU Lesser General Public License for more details. |
|
13 |
|
14 You should have received a copy of the GNU Lesser General Public License |
|
15 along with this library. If not, see <http://www.gnu.org/licenses/>. |
|
16 */ |
|
17 |
|
18 #include "quicktimeaudioplayer.h" |
|
19 #include "quicktimevideoplayer.h" |
|
20 #include "audiograph.h" |
|
21 #include "medianodeevent.h" |
|
22 #include "medianode.h" |
|
23 |
|
24 QT_BEGIN_NAMESPACE |
|
25 |
|
26 namespace Phonon |
|
27 { |
|
28 namespace QT7 |
|
29 { |
|
30 |
|
31 QuickTimeAudioPlayer::QuickTimeAudioPlayer() : AudioNode(0, 1) |
|
32 { |
|
33 m_state = NoMedia; |
|
34 m_videoPlayer = 0; |
|
35 m_audioChannelLayout = 0; |
|
36 m_sliceList = 0; |
|
37 m_sliceCount = 30; |
|
38 m_maxExtractionPacketCount = 4096; |
|
39 m_audioExtractionComplete = false; |
|
40 m_audioEnabled = true; |
|
41 m_samplesRemaining = -1; |
|
42 m_startTime = 0; |
|
43 m_sampleTimeStamp = 0; |
|
44 m_audioUnitIsReset = true; |
|
45 |
|
46 #ifdef QUICKTIME_C_API_AVAILABLE |
|
47 m_audioExtractionRef = 0; |
|
48 #endif |
|
49 } |
|
50 |
|
51 QuickTimeAudioPlayer::~QuickTimeAudioPlayer() |
|
52 { |
|
53 unsetVideoPlayer(); |
|
54 } |
|
55 |
|
56 void QuickTimeAudioPlayer::unsetVideoPlayer() |
|
57 { |
|
58 if (m_audioUnit){ |
|
59 OSStatus err = AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0); |
|
60 BACKEND_ASSERT2(err == noErr, "Could not reset audio player unit when unsetting movie", FATAL_ERROR) |
|
61 } |
|
62 |
|
63 #ifdef QUICKTIME_C_API_AVAILABLE |
|
64 if (m_audioExtractionRef && m_videoPlayer && m_videoPlayer->hasMovie()) |
|
65 MovieAudioExtractionEnd(m_audioExtractionRef); |
|
66 m_audioExtractionRef = 0; |
|
67 #endif |
|
68 |
|
69 if (m_audioChannelLayout){ |
|
70 free(m_audioChannelLayout); |
|
71 m_audioChannelLayout = 0; |
|
72 } |
|
73 |
|
74 if (m_sliceList){ |
|
75 for (int i=0; i<m_sliceCount; i++) |
|
76 free(m_sliceList[i].mBufferList); |
|
77 free(m_sliceList); |
|
78 m_sliceList = 0; |
|
79 } |
|
80 |
|
81 m_videoPlayer = 0; |
|
82 m_audioExtractionComplete = false; |
|
83 m_samplesRemaining = -1; |
|
84 m_sampleTimeStamp = 0; |
|
85 m_state = NoMedia; |
|
86 } |
|
87 |
|
88 void QuickTimeAudioPlayer::enableAudio(bool enable) |
|
89 { |
|
90 // Remember to seek after enabling audio. |
|
91 if (enable == m_audioEnabled) |
|
92 return; |
|
93 |
|
94 m_audioEnabled = enable; |
|
95 if (!enable) |
|
96 flush(); |
|
97 } |
|
98 |
|
99 bool QuickTimeAudioPlayer::audioEnabled() |
|
100 { |
|
101 return m_audioEnabled; |
|
102 } |
|
103 |
|
104 void QuickTimeAudioPlayer::setVideoPlayer(QuickTimeVideoPlayer *videoPlayer) |
|
105 { |
|
106 unsetVideoPlayer(); |
|
107 if (videoPlayer && videoPlayer->hasMovie()){ |
|
108 m_videoPlayer = videoPlayer; |
|
109 initSoundExtraction(); |
|
110 allocateSoundSlices(); |
|
111 m_state = Paused; |
|
112 seek(0); |
|
113 } |
|
114 } |
|
115 |
|
116 QuickTimeVideoPlayer *QuickTimeAudioPlayer::videoPlayer() |
|
117 { |
|
118 return m_videoPlayer; |
|
119 } |
|
120 |
|
121 void QuickTimeAudioPlayer::scheduleAudioToGraph() |
|
122 { |
|
123 if (!m_videoPlayer || !m_audioEnabled || m_audioExtractionComplete || m_state != Playing) |
|
124 return; |
|
125 |
|
126 // Schedule audio slices, and detect if everything went OK. |
|
127 // If not, flag the need for another audio system, but let |
|
128 // the end app know about it: |
|
129 gClearError(); |
|
130 scheduleSoundSlices(); |
|
131 if (gGetErrorType() != NO_ERROR){ |
|
132 gClearError(); |
|
133 if (m_audioGraph) |
|
134 m_audioGraph->setStatusCannotPlay(); |
|
135 } |
|
136 } |
|
137 |
|
138 void QuickTimeAudioPlayer::flush() |
|
139 { |
|
140 // Empty scheduled audio data, so playback |
|
141 // will stop. Call seek to refill data again. |
|
142 if (m_audioUnit){ |
|
143 m_startTime = currentTime(); |
|
144 OSStatus err = AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0); |
|
145 BACKEND_ASSERT2(err == noErr, "Could not reset audio player unit on pause", FATAL_ERROR) |
|
146 m_audioUnitIsReset = true; |
|
147 } |
|
148 } |
|
149 |
|
150 void QuickTimeAudioPlayer::pause() |
|
151 { |
|
152 m_state = Paused; |
|
153 flush(); |
|
154 } |
|
155 |
|
156 void QuickTimeAudioPlayer::play() |
|
157 { |
|
158 m_state = Playing; |
|
159 if (!m_audioEnabled) |
|
160 return; |
|
161 if (m_audioUnitIsReset) |
|
162 seek(m_startTime); |
|
163 else |
|
164 scheduleAudioToGraph(); |
|
165 } |
|
166 |
|
167 bool QuickTimeAudioPlayer::isPlaying() |
|
168 { |
|
169 return m_videoPlayer && m_state == Playing; |
|
170 } |
|
171 |
|
172 void QuickTimeAudioPlayer::seek(quint64 milliseconds) |
|
173 { |
|
174 if (!m_videoPlayer || !m_videoPlayer->hasMovie()) |
|
175 return; |
|
176 if (milliseconds > m_videoPlayer->duration()) |
|
177 milliseconds = m_videoPlayer->duration(); |
|
178 if (!m_audioUnitIsReset && milliseconds == currentTime()) |
|
179 return; |
|
180 |
|
181 m_startTime = milliseconds; |
|
182 |
|
183 // Since the graph may be running (advancing time), there is |
|
184 // no point in seeking if were not going to play immidiatly: |
|
185 if (m_state != Playing) |
|
186 return; |
|
187 if (!m_audioUnit) |
|
188 return; |
|
189 if (!m_audioEnabled || !m_videoPlayer->isSeekable()) |
|
190 return; |
|
191 |
|
192 // Reset (and stop playing): |
|
193 OSStatus err; |
|
194 if (!m_audioUnitIsReset){ |
|
195 err = AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0); |
|
196 BACKEND_ASSERT2(err == noErr, "Could not reset audio player unit before seek", FATAL_ERROR) |
|
197 } |
|
198 m_sampleTimeStamp = 0; |
|
199 for (int i = 0; i < m_sliceCount; i++) |
|
200 m_sliceList[i].mFlags = kScheduledAudioSliceFlag_Complete; |
|
201 |
|
202 // Start to play again immidiatly: |
|
203 AudioTimeStamp timeStamp; |
|
204 memset(&timeStamp, 0, sizeof(timeStamp)); |
|
205 timeStamp.mFlags = kAudioTimeStampSampleTimeValid; |
|
206 timeStamp.mSampleTime = -1; |
|
207 err = AudioUnitSetProperty(m_audioUnit, |
|
208 kAudioUnitProperty_ScheduleStartTimeStamp, kAudioUnitScope_Global, |
|
209 0, &timeStamp, sizeof(timeStamp)); |
|
210 BACKEND_ASSERT2(err == noErr, "Could not set schedule start time stamp on audio player unit", FATAL_ERROR) |
|
211 |
|
212 // Seek back to 'now' in the movie: |
|
213 TimeRecord timeRec; |
|
214 timeRec.scale = m_videoPlayer->timeScale(); |
|
215 timeRec.base = 0; |
|
216 timeRec.value.hi = 0; |
|
217 timeRec.value.lo = (milliseconds / 1000.0f) * timeRec.scale; |
|
218 |
|
219 #ifdef QUICKTIME_C_API_AVAILABLE |
|
220 err = MovieAudioExtractionSetProperty(m_audioExtractionRef, |
|
221 kQTPropertyClass_MovieAudioExtraction_Movie, |
|
222 kQTMovieAudioExtractionMoviePropertyID_CurrentTime, |
|
223 sizeof(TimeRecord), &timeRec); |
|
224 BACKEND_ASSERT2(err == noErr, "Could not set current time on audio player unit", FATAL_ERROR) |
|
225 #endif |
|
226 |
|
227 float durationLeftSec = float(m_videoPlayer->duration() - milliseconds) / 1000.0f; |
|
228 m_samplesRemaining = (durationLeftSec > 0) ? (durationLeftSec * m_audioStreamDescription.mSampleRate) : -1; |
|
229 m_audioExtractionComplete = false; |
|
230 m_audioUnitIsReset = false; |
|
231 scheduleAudioToGraph(); |
|
232 |
|
233 } |
|
234 |
|
235 quint64 QuickTimeAudioPlayer::currentTime() |
|
236 { |
|
237 if (!m_audioUnit){ |
|
238 if (m_videoPlayer) |
|
239 return m_videoPlayer->currentTime(); |
|
240 else |
|
241 return m_startTime; |
|
242 } |
|
243 |
|
244 Float64 currentUnitTime = getTimeInSamples(kAudioUnitProperty_CurrentPlayTime); |
|
245 if (currentUnitTime == -1) |
|
246 currentUnitTime = 0; |
|
247 |
|
248 quint64 cTime = quint64(m_startTime + |
|
249 float(currentUnitTime / float(m_audioStreamDescription.mSampleRate)) * 1000.0f); |
|
250 return (m_videoPlayer && cTime > m_videoPlayer->duration()) ? m_videoPlayer->duration() : cTime; |
|
251 } |
|
252 |
|
253 QString QuickTimeAudioPlayer::currentTimeString() |
|
254 { |
|
255 return QuickTimeVideoPlayer::timeToString(currentTime()); |
|
256 } |
|
257 |
|
258 bool QuickTimeAudioPlayer::hasAudio() |
|
259 { |
|
260 if (!m_videoPlayer) |
|
261 return false; |
|
262 |
|
263 return m_videoPlayer->hasAudio(); |
|
264 } |
|
265 |
|
266 bool QuickTimeAudioPlayer::soundPlayerIsAwailable() |
|
267 { |
|
268 QuickTimeAudioPlayer player; |
|
269 ComponentDescription d = player.getAudioNodeDescription(); |
|
270 return FindNextComponent(0, &d); |
|
271 } |
|
272 |
|
273 ComponentDescription QuickTimeAudioPlayer::getAudioNodeDescription() const |
|
274 { |
|
275 ComponentDescription description; |
|
276 description.componentType = kAudioUnitType_Generator; |
|
277 description.componentSubType = kAudioUnitSubType_ScheduledSoundPlayer; |
|
278 description.componentManufacturer = kAudioUnitManufacturer_Apple; |
|
279 description.componentFlags = 0; |
|
280 description.componentFlagsMask = 0; |
|
281 return description; |
|
282 } |
|
283 |
|
284 void QuickTimeAudioPlayer::initializeAudioUnit() |
|
285 { |
|
286 } |
|
287 |
|
288 bool QuickTimeAudioPlayer::fillInStreamSpecification(AudioConnection *connection, ConnectionSide side) |
|
289 { |
|
290 if (!m_videoPlayer){ |
|
291 if (side == Source) |
|
292 DEBUG_AUDIO_STREAM("QuickTimeAudioPlayer" << int(this) << "is source, but has no movie to use for stream spec fill.") |
|
293 return true; |
|
294 } |
|
295 |
|
296 if (side == Source){ |
|
297 DEBUG_AUDIO_STREAM("QuickTimeAudioPlayer" << int(this) << "is source, and fills in stream spec from movie.") |
|
298 connection->m_sourceStreamDescription = m_audioStreamDescription; |
|
299 connection->m_sourceChannelLayout = (AudioChannelLayout *) malloc(m_audioChannelLayoutSize); |
|
300 memcpy(connection->m_sourceChannelLayout, m_audioChannelLayout, m_audioChannelLayoutSize); |
|
301 connection->m_sourceChannelLayoutSize = m_audioChannelLayoutSize; |
|
302 connection->m_hasSourceSpecification = true; |
|
303 } |
|
304 return true; |
|
305 } |
|
306 |
|
307 long QuickTimeAudioPlayer::regularTaskFrequency(){ |
|
308 if (!m_audioEnabled || !m_audioUnit || (m_audioGraph && m_audioGraph->graphCannotPlay())) |
|
309 return INT_MAX; |
|
310 |
|
311 // Calculate how much audio in |
|
312 // milliseconds our slices can hold: |
|
313 int packetNeedPerSecond = m_audioStreamDescription.mSampleRate / m_maxExtractionPacketCount; |
|
314 long bufferTimeLengthSec = float(m_sliceCount) / float(packetNeedPerSecond); |
|
315 // Make sure we also get some time to fill the |
|
316 // buffer, so divide the time by two: |
|
317 return (bufferTimeLengthSec * (1000 / 2)); |
|
318 } |
|
319 |
|
320 void QuickTimeAudioPlayer::initSoundExtraction() |
|
321 { |
|
322 #ifdef QUICKTIME_C_API_AVAILABLE |
|
323 |
|
324 // Initilize the extraction: |
|
325 OSStatus err = noErr; |
|
326 err = MovieAudioExtractionBegin([m_videoPlayer->qtMovie() quickTimeMovie], 0, &m_audioExtractionRef); |
|
327 BACKEND_ASSERT2(err == noErr, "Could not start audio extraction on audio player unit", FATAL_ERROR) |
|
328 m_discrete = false; |
|
329 #if 0 |
|
330 // Extract all channels as descrete: |
|
331 err = MovieAudioExtractionSetProperty(audioExtractionRef, |
|
332 kQTPropertyClass_MovieAudioExtraction_Movie, |
|
333 kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete, |
|
334 sizeof (discrete), |
|
335 &discrete); |
|
336 BACKEND_ASSERT2(err == noErr, "Could not set channels discrete on audio player unit", FATAL_ERROR) |
|
337 #endif |
|
338 |
|
339 // Get the size of the audio channel layout (may include offset): |
|
340 err = MovieAudioExtractionGetPropertyInfo(m_audioExtractionRef, |
|
341 kQTPropertyClass_MovieAudioExtraction_Audio, |
|
342 kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, |
|
343 0, &m_audioChannelLayoutSize, 0); |
|
344 BACKEND_ASSERT2(err == noErr, "Could not get channel layout size from audio extraction", FATAL_ERROR) |
|
345 |
|
346 // Allocate memory for the layout |
|
347 m_audioChannelLayout = (AudioChannelLayout *) calloc(1, m_audioChannelLayoutSize); |
|
348 BACKEND_ASSERT2(m_audioChannelLayout, "Could not allocate memory for channel layout on audio player unit", FATAL_ERROR) |
|
349 |
|
350 // Get the layout: |
|
351 err = MovieAudioExtractionGetProperty(m_audioExtractionRef, |
|
352 kQTPropertyClass_MovieAudioExtraction_Audio, |
|
353 kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, |
|
354 m_audioChannelLayoutSize, m_audioChannelLayout, 0); |
|
355 BACKEND_ASSERT2(err == noErr, "Could not get channel layout from audio extraction", FATAL_ERROR) |
|
356 |
|
357 // Get audio stream description: |
|
358 err = MovieAudioExtractionGetProperty(m_audioExtractionRef, |
|
359 kQTPropertyClass_MovieAudioExtraction_Audio, |
|
360 kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, |
|
361 sizeof(m_audioStreamDescription), &m_audioStreamDescription, 0); |
|
362 BACKEND_ASSERT2(err == noErr, "Could not get audio stream description from audio extraction", FATAL_ERROR) |
|
363 |
|
364 #endif // QUICKTIME_C_API_AVAILABLE |
|
365 } |
|
366 |
|
367 void QuickTimeAudioPlayer::allocateSoundSlices() |
|
368 { |
|
369 #ifdef QUICKTIME_C_API_AVAILABLE |
|
370 |
|
371 // m_sliceList will contain a specified number of ScheduledAudioSlice-s that each can |
|
372 // carry audio from extraction, and be scheduled for playback at an audio unit. |
|
373 // Each ScheduledAudioSlice will contain several audio buffers, one for each sound channel. |
|
374 // Each buffer will carry (at most) a specified number of sound packets, and each packet can |
|
375 // contain one or more frames. |
|
376 |
|
377 // Create a list of ScheduledAudioSlices: |
|
378 m_sliceList = (ScheduledAudioSlice *) calloc(m_sliceCount, sizeof(ScheduledAudioSlice)); |
|
379 BACKEND_ASSERT2(m_sliceList, "Could not allocate memory for audio slices", FATAL_ERROR) |
|
380 bzero(m_sliceList, m_sliceCount * sizeof(ScheduledAudioSlice)); |
|
381 |
|
382 // Calculate the size of the different structures needed: |
|
383 int packetsBufferSize = m_maxExtractionPacketCount * m_audioStreamDescription.mBytesPerPacket; |
|
384 int channels = m_audioStreamDescription.mChannelsPerFrame; |
|
385 int audioBufferListSize = int(sizeof(AudioBufferList) + (channels-1) * sizeof(AudioBuffer)); |
|
386 int mallocSize = audioBufferListSize + (packetsBufferSize * m_audioStreamDescription.mChannelsPerFrame); |
|
387 |
|
388 // Round off to Altivec sizes: |
|
389 packetsBufferSize = int(((packetsBufferSize + 15) / 16) * 16); |
|
390 audioBufferListSize = int(((audioBufferListSize + 15) / 16) * 16); |
|
391 |
|
392 for (int sliceIndex = 0; sliceIndex < m_sliceCount; ++sliceIndex){ |
|
393 // Create the memory chunk for this audio slice: |
|
394 AudioBufferList *audioBufferList = (AudioBufferList*) calloc(1, mallocSize); |
|
395 BACKEND_ASSERT2(audioBufferList, "Could not allocate memory for audio buffer list", FATAL_ERROR) |
|
396 |
|
397 // The AudioBufferList contains an AudioBuffer for each channel in the audio stream: |
|
398 audioBufferList->mNumberBuffers = m_audioStreamDescription.mChannelsPerFrame; |
|
399 for (uint i = 0; i < audioBufferList->mNumberBuffers; ++i){ |
|
400 audioBufferList->mBuffers[i].mNumberChannels = 1; |
|
401 audioBufferList->mBuffers[i].mData = (char *) audioBufferList + audioBufferListSize + (i * packetsBufferSize); |
|
402 audioBufferList->mBuffers[i].mDataByteSize = packetsBufferSize; |
|
403 } |
|
404 |
|
405 m_sliceList[sliceIndex].mBufferList = audioBufferList; |
|
406 m_sliceList[sliceIndex].mNumberFrames = m_maxExtractionPacketCount; |
|
407 m_sliceList[sliceIndex].mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid; |
|
408 m_sliceList[sliceIndex].mCompletionProcUserData = 0; |
|
409 m_sliceList[sliceIndex].mCompletionProc = 0; |
|
410 m_sliceList[sliceIndex].mFlags = kScheduledAudioSliceFlag_Complete; |
|
411 m_sliceList[sliceIndex].mReserved = 0; |
|
412 } |
|
413 |
|
414 #endif // QUICKTIME_C_API_AVAILABLE |
|
415 } |
|
416 |
|
417 void QuickTimeAudioPlayer::scheduleSoundSlices() |
|
418 { |
|
419 #ifdef QUICKTIME_C_API_AVAILABLE |
|
420 |
|
421 PhononAutoReleasePool pool; |
|
422 // For each completed (or never used) slice, fill and schedule it. |
|
423 for (int sliceIndex = 0; sliceIndex < m_sliceCount; ++sliceIndex){ |
|
424 if (m_sliceList[sliceIndex].mFlags & kScheduledAudioSliceFlag_Complete){ |
|
425 if (m_samplesRemaining == 0) |
|
426 m_audioExtractionComplete = true; |
|
427 |
|
428 if (!m_audioExtractionComplete){ |
|
429 // Determine how many samples to read: |
|
430 int samplesCount = m_samplesRemaining; |
|
431 if ((samplesCount > m_maxExtractionPacketCount) || (samplesCount == -1)) |
|
432 samplesCount = m_maxExtractionPacketCount; |
|
433 m_sliceList[sliceIndex].mTimeStamp.mSampleTime = m_sampleTimeStamp; |
|
434 |
|
435 // Reset buffer sizes: |
|
436 int byteSize = samplesCount * m_audioStreamDescription.mBytesPerPacket; |
|
437 for (uint i = 0; i < m_sliceList[sliceIndex].mBufferList->mNumberBuffers; ++i) |
|
438 m_sliceList[sliceIndex].mBufferList->mBuffers[i].mDataByteSize = byteSize; |
|
439 |
|
440 // Do the extraction: |
|
441 UInt32 flags = 0; |
|
442 UInt32 samplesRead = samplesCount; |
|
443 OSStatus err = MovieAudioExtractionFillBuffer( |
|
444 m_audioExtractionRef, &samplesRead, m_sliceList[sliceIndex].mBufferList, &flags); |
|
445 BACKEND_ASSERT2(err == noErr, "Could not fill audio buffers from audio extraction", FATAL_ERROR) |
|
446 m_audioExtractionComplete = (flags & kQTMovieAudioExtractionComplete); |
|
447 |
|
448 // Play the slice: |
|
449 if (samplesRead != 0 && m_audioUnit != 0){ |
|
450 m_sliceList[sliceIndex].mNumberFrames = samplesRead; |
|
451 err = AudioUnitSetProperty(m_audioUnit, |
|
452 kAudioUnitProperty_ScheduleAudioSlice, kAudioUnitScope_Global, |
|
453 0, &m_sliceList[sliceIndex], sizeof(ScheduledAudioSlice)); |
|
454 BACKEND_ASSERT2(err == noErr, "Could not schedule audio buffers on audio unit", FATAL_ERROR) |
|
455 } else |
|
456 m_sliceList[sliceIndex].mFlags = kScheduledAudioSliceFlag_Complete; |
|
457 |
|
458 // Move the window: |
|
459 m_sampleTimeStamp += samplesRead; |
|
460 if (m_samplesRemaining != -1) |
|
461 m_samplesRemaining -= samplesRead; |
|
462 } |
|
463 } |
|
464 } |
|
465 |
|
466 #endif // QUICKTIME_C_API_AVAILABLE |
|
467 } |
|
468 |
|
469 void QuickTimeAudioPlayer::mediaNodeEvent(const MediaNodeEvent *event) |
|
470 { |
|
471 switch (event->type()){ |
|
472 case MediaNodeEvent::AudioGraphAboutToBeDeleted: |
|
473 case MediaNodeEvent::AboutToRestartAudioStream: |
|
474 case MediaNodeEvent::StartConnectionChange: |
|
475 m_startTime = currentTime(); |
|
476 break; |
|
477 case MediaNodeEvent::AudioGraphInitialized: |
|
478 case MediaNodeEvent::RestartAudioStreamRequest: |
|
479 case MediaNodeEvent::EndConnectionChange: |
|
480 if (m_state == Playing) |
|
481 seek(m_startTime); |
|
482 break; |
|
483 default: |
|
484 break; |
|
485 } |
|
486 } |
|
487 |
|
488 }} |
|
489 |
|
490 QT_END_NAMESPACE |
|
491 |