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 "quicktimevideoplayer.h"
19
#include "mediaobject.h"
20
#include "videowidget.h"
21
#include "audiodevice.h"
22
#include "quicktimestreamreader.h"
23
24
#include <QtCore/QCoreApplication>
25
#include <QtCore/QEventLoop>
26
#include <QtCore/QFileInfo>
27
#include <QtCore/QUrl>
28
#include <QtOpenGL/QGLContext>
29
30
#import <QTKit/QTTrack.h>
31
#import <QTKit/QTMedia.h>
32
#import <QuartzCore/CIContext.h>
33
#import <QuartzCore/CIFilter.h>
34
35
#ifdef QUICKTIME_C_API_AVAILABLE
36
    #include <QuickTime/QuickTime.h>
37
    #undef check // avoid name clash;
38
    #include <AGL/agl.h>
39
#endif
40
41
QT_BEGIN_NAMESPACE
42
43
namespace Phonon
44
{
45
namespace QT7
46
{
47
48
// Defined in videowidget.cpp:
49
QGLWidget *PhononSharedQGLWidget();
50
51
QuickTimeVideoPlayer::QuickTimeVideoPlayer() : QObject(0)
52
{
53
    m_state = NoMedia;
54
    m_mediaSource = MediaSource();
55
    m_QTMovie = 0;
56
    m_streamReader = 0;
57
    m_playbackRate = 1.0f;
58
    m_masterVolume = 1.0f;
59
    m_relativeVolume = 1.0f;
60
    m_currentTime = 0;
61
    m_mute = false;
62
    m_audioEnabled = false;
63
    m_hasVideo = false;
64
    m_playbackRateSat = false;
65
    m_isDrmProtected = false;
66
    m_isDrmAuthorized = true;
67
	m_primaryRenderingTarget = 0;
68
	m_primaryRenderingCIImage = 0;
69
    m_QImagePixelBuffer = 0;
70
71
#ifdef QUICKTIME_C_API_AVAILABLE
72
    OSStatus err = EnterMovies();
73
    BACKEND_ASSERT2(err == noErr, "Could not initialize QuickTime", FATAL_ERROR)
74
	createVisualContext();
75
#endif
76
}
77
78
QuickTimeVideoPlayer::~QuickTimeVideoPlayer()
79
{
80
    unsetVideo();
81
    [(NSObject*)m_primaryRenderingTarget release];
82
    m_primaryRenderingTarget = 0;
83
#ifdef QUICKTIME_C_API_AVAILABLE
84
    if (m_visualContext)
85
        CFRelease(m_visualContext);
86
#endif
87
}
88
89
void QuickTimeVideoPlayer::createVisualContext()
90
{
91
#ifdef QUICKTIME_C_API_AVAILABLE
92
	PhononSharedQGLWidget()->makeCurrent();
93
94
	PhononAutoReleasePool pool;
95
    CGLContextObj cglContext = CGLGetCurrentContext();
96
	NSOpenGLPixelFormat *nsglPixelFormat = [NSOpenGLView defaultPixelFormat];
97
    CGLPixelFormatObj cglPixelFormat = static_cast<CGLPixelFormatObj>([nsglPixelFormat CGLPixelFormatObj]);
98
	BACKEND_ASSERT2(cglContext, "Could not get current CoreVideo GL context (OpenGL)", FATAL_ERROR)
99
	BACKEND_ASSERT2(cglPixelFormat, "Could not get current CoreVideo pixel format (OpenGL)", FATAL_ERROR)
100
101
    CFTypeRef keys[] = { kQTVisualContextWorkingColorSpaceKey };
102
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
103
    CFDictionaryRef textureContextAttributes = CFDictionaryCreate(kCFAllocatorDefault,
104
        (const void **)keys,
105
        (const void **)&colorSpace, 1,
106
        &kCFTypeDictionaryKeyCallBacks,
107
        &kCFTypeDictionaryValueCallBacks);
108
109
	OSStatus err = QTOpenGLTextureContextCreate(kCFAllocatorDefault, cglContext,
110
        cglPixelFormat, textureContextAttributes, &m_visualContext);
111
    CFRelease(textureContextAttributes);
112
    BACKEND_ASSERT2(err == noErr, "Could not create visual context (OpenGL)", FATAL_ERROR)
113
#endif // QUICKTIME_C_API_AVAILABLE
114
}
115
116
bool QuickTimeVideoPlayer::videoFrameChanged()
117
{
118
    if (!m_QTMovie || !m_hasVideo)
119
        return false;
120
121
#ifdef QUICKTIME_C_API_AVAILABLE
122
	if (m_primaryRenderingTarget)
123
		return true;
124
    if (!m_visualContext)
125
		return false;
126
127
    QTVisualContextTask(m_visualContext);
128
    return QTVisualContextIsNewImageAvailable(m_visualContext, 0);
129
130
#elif defined(QT_MAC_USE_COCOA)
131
    return true;
132
133
#else
134
    return false;
135
#endif
136
}
137
138
CVOpenGLTextureRef QuickTimeVideoPlayer::currentFrameAsCVTexture()
139
{
140
#ifdef QUICKTIME_C_API_AVAILABLE
141
    if (!m_visualContext)
142
        return 0;
143
    CVOpenGLTextureRef texture = 0;
144
    OSStatus err = QTVisualContextCopyImageForTime(m_visualContext, 0, 0, &texture);
145
    BACKEND_ASSERT3(err == noErr, "Could not copy image for time in QuickTime player", FATAL_ERROR, 0)
146
    return texture;
147
148
#else
149
    return 0;
150
#endif
151
}
152
153
QImage QuickTimeVideoPlayer::currentFrameAsQImage()
154
{
155
#ifdef QUICKTIME_C_API_AVAILABLE
156
    QGLContext *prevContext = const_cast<QGLContext *>(QGLContext::currentContext());
157
    CVOpenGLTextureRef texture = currentFrameAsCVTexture();
158
    GLenum target = CVOpenGLTextureGetTarget(texture);
159
    GLfloat lowerLeft[2], lowerRight[2], upperRight[2], upperLeft[2];
160
161
    if (!m_QImagePixelBuffer){
162
        m_QImagePixelBuffer = new QGLPixelBuffer(videoRect().size(), QGLFormat::defaultFormat(), PhononSharedQGLWidget());
163
        m_QImagePixelBuffer->makeCurrent();
164
        glEnable(target);
165
        glDisable(GL_BLEND);
166
        glDisable(GL_CULL_FACE);
167
    } else {
168
        m_QImagePixelBuffer->makeCurrent();
169
    }
170
171
    CVOpenGLTextureGetCleanTexCoords(texture, upperLeft, upperRight, lowerRight, lowerLeft);
172
    glBindTexture(target, CVOpenGLTextureGetName(texture));
173
    glBegin(GL_QUADS);
174
        glTexCoord2f(lowerLeft[0], lowerLeft[1]);
175
        glVertex2i(-1, 1);
176
        glTexCoord2f(lowerRight[0], lowerRight[1]);
177
        glVertex2i(1, 1);
178
        glTexCoord2f(upperRight[0], upperRight[1]);
179
        glVertex2i(1, -1);
180
        glTexCoord2f(upperLeft[0], upperLeft[1]);
181
        glVertex2i(-1, -1);
182
    glEnd();
183
184
    QImage image = m_QImagePixelBuffer->toImage();
185
    CVOpenGLTextureRelease(texture);
186
    // Because of QuickTime, m_QImagePixelBuffer->doneCurrent() will fail.
187
    // So we store, and restore, the context our selves:
188
    prevContext->makeCurrent();
189
    return image;
190
#else
191
	CIImage *img = (CIImage *)currentFrameAsCIImage();
192
	if (!img)
193
		return QImage();
194
195
	NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithCIImage:img];
196
	CGRect bounds = [img extent];
197
	QImage qImg([bitmap bitmapData], bounds.size.width, bounds.size.height, QImage::Format_ARGB32);
198
	QImage swapped = qImg.rgbSwapped();
199
	[bitmap release];
200
	[img release];
201
	return swapped;
202
#endif
203
}
204
205
void QuickTimeVideoPlayer::setPrimaryRenderingCIImage(void *ciImage)
206
{
207
	[(CIImage *)m_primaryRenderingCIImage release];
208
	m_primaryRenderingCIImage = ciImage;
209
	[(CIImage *)m_primaryRenderingCIImage retain];
210
}
211
212
void QuickTimeVideoPlayer::setPrimaryRenderingTarget(NSObject *target)
213
{
214
	[(NSObject*)m_primaryRenderingTarget release];
215
	m_primaryRenderingTarget = target;
216
	[(NSObject*)m_primaryRenderingTarget retain];
217
}
218
219
void *QuickTimeVideoPlayer::primaryRenderingCIImage()
220
{
221
	return m_primaryRenderingCIImage;
222
}
223
224
void *QuickTimeVideoPlayer::currentFrameAsCIImage()
225
{
226
    if (!m_QTMovie)
227
        return 0;
228
229
#if defined(QT_MAC_USE_COCOA)
230
	if (m_primaryRenderingCIImage){
231
		CIImage *img = (CIImage *)m_primaryRenderingCIImage;
232
		if (m_brightness || m_contrast || m_saturation){
233
			CIFilter *colorFilter = [CIFilter filterWithName:@"CIColorControls"];
234
			[colorFilter setValue:[NSNumber numberWithFloat:m_brightness] forKey:@"inputBrightness"];
235
			[colorFilter setValue:[NSNumber numberWithFloat:(m_contrast < 1) ? m_contrast : 1 + ((m_contrast-1)*3)] forKey:@"inputContrast"];
236
			[colorFilter setValue:[NSNumber numberWithFloat:m_saturation] forKey:@"inputSaturation"];
237
			[colorFilter setValue:img forKey:@"inputImage"];
238
			img = [colorFilter valueForKey:@"outputImage"];
239
		}
240
		if (m_hue){
241
			CIFilter *colorFilter = [CIFilter filterWithName:@"CIHueAdjust"];
242
			[colorFilter setValue:[NSNumber numberWithFloat:(m_hue * 3.14)] forKey:@"inputAngle"];
243
			[colorFilter setValue:img forKey:@"inputImage"];
244
			img = [colorFilter valueForKey:@"outputImage"];
245
		}
246
		return [img retain];
247
	}
248
#endif
249
250
#ifdef QUICKTIME_C_API_AVAILABLE
251
	CVOpenGLTextureRef cvImg = currentFrameAsCVTexture();
252
	CIImage *img = [[CIImage alloc] initWithCVImageBuffer:cvImg];
253
	CVOpenGLTextureRelease(cvImg);
254
	return img;	
255
#else
256
	return 0;
257
#endif
258
}
259
260
GLuint QuickTimeVideoPlayer::currentFrameAsGLTexture()
261
{
262
	CIImage *img = (CIImage *)currentFrameAsCIImage();
263
	if (!img)
264
		return 0;
265
266
	NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithCIImage:img];
267
    GLuint texName = 0;
268
    glPixelStorei(GL_UNPACK_ROW_LENGTH, [bitmap pixelsWide]);
269
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
270
    glGenTextures(1, &texName);
271
    glBindTexture(GL_TEXTURE_RECTANGLE_EXT, texName);
272
    glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER,  GL_LINEAR);
273
274
    int samplesPerPixel = [bitmap samplesPerPixel];
275
    if (![bitmap isPlanar] && (samplesPerPixel == 3 || samplesPerPixel == 4)){
276
        glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, 
277
            samplesPerPixel == 4 ? GL_RGBA8 : GL_RGB8,
278
            [bitmap pixelsWide], [bitmap pixelsHigh],
279
            0, samplesPerPixel == 4 ? GL_RGBA : GL_RGB,
280
            GL_UNSIGNED_BYTE, [bitmap bitmapData]);
281
    } else {
282
        // Handle other bitmap formats.
283
    }
284
285
    [bitmap release];
286
	[img release];
287
    return texName;
288
}
289
290
void QuickTimeVideoPlayer::setMasterVolume(float volume)
291
{
292
    setVolume(volume, m_relativeVolume);
293
}
294
295
void QuickTimeVideoPlayer::setRelativeVolume(float volume)
296
{
297
    setVolume(m_masterVolume, volume);
298
}
299
300
void QuickTimeVideoPlayer::setVolume(float masterVolume, float relativeVolume)
301
{
302
    m_masterVolume = masterVolume;
303
    m_relativeVolume = relativeVolume;
304
    if (!m_QTMovie || !m_audioEnabled || m_mute)
305
        return;                
306
    [m_QTMovie setVolume:(m_masterVolume * m_relativeVolume)];
307
}
308
309
void QuickTimeVideoPlayer::setMute(bool mute)
310
{
311
    m_mute = mute;
312
    if (!m_QTMovie || m_state != Playing || !m_audioEnabled)
313
        return;
314
315
    // Work-around bug that happends if you set/unset mute
316
    // before movie is playing, and audio is not played 
317
    // through graph. Then audio is delayed.
318
    [m_QTMovie setMuted:mute];
319
    [m_QTMovie setVolume:(mute ? 0 : m_masterVolume * m_relativeVolume)];
320
}
321
322
void QuickTimeVideoPlayer::enableAudio(bool enable)
323
{
324
    m_audioEnabled = enable;
325
    if (!m_QTMovie || m_state != Playing)
326
        return;
327
328
    // Work-around bug that happends if you set/unset mute
329
    // before movie is playing, and audio is not played 
330
    // through graph. Then audio is delayed.
331
    [m_QTMovie setMuted:(!enable || m_mute)];
332
    [m_QTMovie setVolume:((!enable || m_mute) ? 0 : m_masterVolume * m_relativeVolume)];
333
}
334
335
bool QuickTimeVideoPlayer::audioEnabled()
336
{
337
    return m_audioEnabled;
338
}
339
340
bool QuickTimeVideoPlayer::setAudioDevice(int id)
341
{
342
    if (!m_QTMovie)
343
        return false;
344
345
#ifdef QUICKTIME_C_API_AVAILABLE
346
    // The following code will not work for some media codecs that
347
    // typically mingle audio/video frames (e.g mpeg).
348
    CFStringRef idString = PhononCFString::toCFStringRef(AudioDevice::deviceUID(id));        
349
    QTAudioContextRef context;
350
    QTAudioContextCreateForAudioDevice(kCFAllocatorDefault, idString, 0, &context);
351
    OSStatus err = SetMovieAudioContext([m_QTMovie quickTimeMovie], context);
352
    CFRelease(context);
353
    if (err != noErr)
354
        return false;
355
    return true;
356
#else
357
    Q_UNUSED(id);
358
    return false;
359
#endif
360
}
361
362
void QuickTimeVideoPlayer::setColors(qreal brightness, qreal contrast, qreal hue, qreal saturation)
363
{
364
    if (!m_QTMovie)
365
        return;
366
367
    // 0 is default value for the colors
368
    // in phonon, so adjust scale:
369
    contrast += 1;
370
    saturation += 1;
371
372
	m_brightness = brightness;
373
	m_contrast = contrast;
374
	m_hue = hue;
375
	m_saturation = saturation;
376
	
377
#ifdef QUICKTIME_C_API_AVAILABLE
378
    Float32 value;
379
    value = brightness;
380
    SetMovieVisualBrightness([m_QTMovie quickTimeMovie], value, 0);
381
    value = contrast;
382
    SetMovieVisualContrast([m_QTMovie quickTimeMovie], value, 0);
383
    value = hue;
384
    SetMovieVisualHue([m_QTMovie quickTimeMovie], value, 0);
385
    value = saturation;
386
    SetMovieVisualSaturation([m_QTMovie quickTimeMovie], value, 0);
387
#endif
388
}
389
390
QRect QuickTimeVideoPlayer::videoRect() const
391
{
392
    if (!m_QTMovie)
393
        return QRect();
394
395
	PhononAutoReleasePool pool;
396
    NSSize size = [[m_QTMovie attributeForKey:@"QTMovieCurrentSizeAttribute"] sizeValue];
397
    return QRect(0, 0, size.width, size.height);
398
}
399
400
void QuickTimeVideoPlayer::unsetVideo()
401
{
402
    if (!m_QTMovie)
403
        return;
404
405
    [m_QTMovie release];
406
	m_QTMovie = 0;
407
    delete m_streamReader;
408
    m_streamReader = 0;
409
    m_currentTime = 0;
410
    m_state = NoMedia;
411
    m_isDrmProtected = false;
412
    m_isDrmAuthorized = true;
413
    m_mediaSource = MediaSource();
414
	[(CIImage *)m_primaryRenderingCIImage release];
415
	m_primaryRenderingCIImage = 0;
416
    delete m_QImagePixelBuffer;
417
    m_QImagePixelBuffer = 0;
418
}
419
420
QuickTimeVideoPlayer::State QuickTimeVideoPlayer::state() const
421
{
422
    return m_state;
423
}
424
425
quint64 QuickTimeVideoPlayer::timeLoaded()
426
{
427
    if (!m_QTMovie)
428
        return 0;
429
#ifdef QUICKTIME_C_API_AVAILABLE
430
    TimeValue value;
431
    GetMaxLoadedTimeInMovie([m_QTMovie quickTimeMovie], &value);
432
    quint64 loaded = static_cast<quint64>(float(value) / float(GetMovieTimeScale([m_QTMovie quickTimeMovie])) * 1000.0f);
433
    return (loaded == INT_MAX) ? 0 : loaded;
434
#else
435
    return 0;
436
#endif
437
}
438
439
float QuickTimeVideoPlayer::percentageLoaded()
440
{
441
    if (!m_QTMovie || !isSeekable())
442
        return 0;
443
#ifdef QUICKTIME_C_API_AVAILABLE
444
    TimeValue loaded;
445
    GetMaxLoadedTimeInMovie([m_QTMovie quickTimeMovie], &loaded);
446
    float duration = GetMovieDuration([m_QTMovie quickTimeMovie]);
447
    return duration ? float(loaded) / duration : 0;
448
#else
449
    return 0;
450
#endif
451
}
452
453
void QuickTimeVideoPlayer::waitStatePlayable()
454
{
455
#if defined(QT_MAC_USE_COCOA)
456
    long state = [[m_QTMovie attributeForKey:@"QTMovieLoadStateAttribute"] longValue];
457
    while (state != QTMovieLoadStateError && state < QTMovieLoadStatePlayable)
458
        state = [[m_QTMovie attributeForKey:@"QTMovieLoadStateAttribute"] longValue];
459
#elif defined(QUICKTIME_C_API_AVAILABLE)
460
    long state = GetMovieLoadState([m_QTMovie quickTimeMovie]);
461
    while (state != kMovieLoadStateError && state < kMovieLoadStatePlayable){
462
        MoviesTask(0, 0);
463
        state = GetMovieLoadState([m_QTMovie quickTimeMovie]);
464
    }
465
#endif
466
}
467
468
bool QuickTimeVideoPlayer::movieNotLoaded()
469
{
470
    if (!m_QTMovie)
471
        return true;
472
473
#if defined(QT_MAC_USE_COCOA)
474
    long state = [[m_QTMovie attributeForKey:@"QTMovieLoadStateAttribute"] longValue];
475
    return state == QTMovieLoadStateError;
476
#elif defined(QUICKTIME_C_API_AVAILABLE)
477
    long state = GetMovieLoadState([m_QTMovie quickTimeMovie]);
478
    return state == kMovieLoadStateError;
479
#endif
480
}
481
482
void QuickTimeVideoPlayer::setError(NSError *error)
483
{
484
    if (!error)
485
        return;
486
    QString desc = QString::fromUtf8([[error localizedDescription] UTF8String]);
487
    if (desc == "The file is not a movie file.")
488
        desc = QLatin1String("Could not decode media source.");
489
    else if (desc == "A necessary data reference could not be resolved."){
490
		if (codecExistsAccordingToSuffix(mediaSourcePath()))
491
            desc = QLatin1String("Could not locate media source.");
492
		else
493
            desc = QLatin1String("Could not decode media source.");
494
    } else if (desc == "You do not have sufficient permissions for this operation.")
495
        desc = QLatin1String("Could not open media source.");
496
    SET_ERROR(desc, FATAL_ERROR)
497
}
498
499
bool QuickTimeVideoPlayer::errorOccured()
500
{
501
    if (gGetErrorType() != NO_ERROR){
502
        return true;
503
    } else if (movieNotLoaded()){
504
        SET_ERROR("Could not open media source.", FATAL_ERROR)
505
        return true;
506
    }
507
	return false;
508
}
509
510
bool QuickTimeVideoPlayer::codecExistsAccordingToSuffix(const QString &fileName)
511
{
512
	PhononAutoReleasePool pool;
513
	NSArray *fileTypes = [QTMovie movieFileTypes:QTIncludeAllTypes];
514
	for (uint i=0; i<[fileTypes count]; ++i){
515
		NSString *type = [fileTypes objectAtIndex:i];
516
		QString formattedType = QString::fromUtf8([type UTF8String]);
517
		formattedType.remove('\'').remove('.');
518
		if (fileName.endsWith(QChar('.') + formattedType, Qt::CaseInsensitive))
519
			return true;
520
	}
521
	return false;
522
}
523
524
void QuickTimeVideoPlayer::setMediaSource(const MediaSource &mediaSource)
525
{
526
    PhononAutoReleasePool pool;
527
    unsetVideo();
528
    m_mediaSource = mediaSource;
529
    if (mediaSource.type() == MediaSource::Empty || mediaSource.type() == MediaSource::Invalid){
530
        m_state = NoMedia;
531
        return;
532
    }
533
    openMovieFromCurrentMediaSource();
534
    if (errorOccured()){
535
        unsetVideo();
536
        return;
537
    }
538
539
#ifdef QUICKTIME_C_API_AVAILABLE
540
    if (m_visualContext)
541
        SetMovieVisualContext([m_QTMovie quickTimeMovie], m_visualContext);
542
#endif
543
544
    waitStatePlayable();
545
    if (errorOccured()){
546
        unsetVideo();
547
        return;
548
    }
549
550
    readProtection();
551
    preRollMovie();
552
    if (errorOccured()){
553
        unsetVideo();
554
        return;
555
    }
556
557
    if (!m_playbackRateSat)
558
        m_playbackRate = prefferedPlaybackRate();
559
    checkIfVideoAwailable();
560
    enableAudio(m_audioEnabled);
561
    setMute(m_mute);
562
    setVolume(m_masterVolume, m_relativeVolume);
563
    pause();
564
}
565
566
void QuickTimeVideoPlayer::openMovieFromCurrentMediaSource()
567
{
568
    switch (m_mediaSource.type()){
569
    case MediaSource::LocalFile:
570
        openMovieFromFile();
571
        break;
572
    case MediaSource::Url:
573
        openMovieFromUrl();
574
        break;
575
    case MediaSource::Disc:
576
        CASE_UNSUPPORTED("Could not open media source.", FATAL_ERROR)
577
        break;
578
    case MediaSource::Stream:
579
        openMovieFromStream();
580
        break;
581
    case MediaSource::Empty:
582
    case MediaSource::Invalid:
583
        break;
584
    }
585
}
586
587
QString QuickTimeVideoPlayer::mediaSourcePath()
588
{
589
    switch (m_mediaSource.type()){
590
    case MediaSource::LocalFile:{
591
        QFileInfo fileInfo(m_mediaSource.fileName());
592
        return fileInfo.isSymLink() ? fileInfo.symLinkTarget() : fileInfo.canonicalFilePath();
593
        break;}
594
    case MediaSource::Url:
595
		return m_mediaSource.url().toEncoded();
596
        break;
597
    default:
598
        break;
599
    }
600
	return QString();
601
}
602
603
void QuickTimeVideoPlayer::openMovieFromDataRef(QTDataReference *dataRef)
604
{
605
    PhononAutoReleasePool pool;
606
    NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
607
                dataRef, QTMovieDataReferenceAttribute,
608
                [NSNumber numberWithBool:YES], QTMovieOpenAsyncOKAttribute,
609
                [NSNumber numberWithBool:YES], QTMovieIsActiveAttribute,
610
                [NSNumber numberWithBool:YES], QTMovieResolveDataRefsAttribute,
611
                [NSNumber numberWithBool:YES], QTMovieDontInteractWithUserAttribute,
612
                nil];
613
614
    NSError *err = 0;
615
    m_QTMovie = [[QTMovie movieWithAttributes:attr error:&err] retain];
616
    if (err){
617
        [m_QTMovie release];
618
        m_QTMovie = 0;
619
        setError(err);
620
    }
621
}
622
623
void QuickTimeVideoPlayer::openMovieFromData(QByteArray *data, char *fileType)
624
{
625
    PhononAutoReleasePool pool;
626
    NSString *type = [NSString stringWithUTF8String:fileType];
627
    NSData *nsData = [NSData dataWithBytesNoCopy:data->data() length:data->size() freeWhenDone:NO];
628
    QTDataReference *dataRef = [QTDataReference dataReferenceWithReferenceToData:nsData name:type MIMEType:@""];
629
    openMovieFromDataRef(dataRef);
630
}
631
632
void QuickTimeVideoPlayer::openMovieFromDataGuessType(QByteArray *data)
633
{
634
    // It turns out to be better to just try the standard file types rather
635
    // than using e.g [QTMovie movieFileTypes:QTIncludeCommonTypes]. Some
636
    // codecs *think* they can decode the stream, and crash...
637
#define TryOpenMovieWithCodec(type) gClearError(); \
638
    openMovieFromData(data, "."type); \
639
    if (m_QTMovie) return;
640
641
    TryOpenMovieWithCodec("avi");
642
    TryOpenMovieWithCodec("mp4");
643
    TryOpenMovieWithCodec("m4p");
644
    TryOpenMovieWithCodec("m1s");
645
    TryOpenMovieWithCodec("mp3");
646
    TryOpenMovieWithCodec("mpeg");
647
    TryOpenMovieWithCodec("mov");
648
    TryOpenMovieWithCodec("ogg");
649
    TryOpenMovieWithCodec("wav");
650
    TryOpenMovieWithCodec("wmv");
651
#undef TryOpenMovieWithCodec(type)
652
}
653
654
void QuickTimeVideoPlayer::openMovieFromFile()
655
{
656
    NSString *nsFilename = (NSString *)PhononCFString::toCFStringRef(mediaSourcePath());
657
    QTDataReference *dataRef = [QTDataReference dataReferenceWithReferenceToFile:nsFilename];
658
    openMovieFromDataRef(dataRef);
659
}
660
661
void QuickTimeVideoPlayer::openMovieFromUrl()
662
{
663
    PhononAutoReleasePool pool;
664
    NSString *urlString = (NSString *)PhononCFString::toCFStringRef(mediaSourcePath());
665
    NSURL *url = [NSURL URLWithString: urlString];
666
    QTDataReference *dataRef = [QTDataReference dataReferenceWithReferenceToURL:url];
667
    openMovieFromDataRef(dataRef);
668
}
669
670
void QuickTimeVideoPlayer::openMovieFromStream()
671
{
672
    m_streamReader = new QuickTimeStreamReader(m_mediaSource);
673
    if (!m_streamReader->readAllData())
674
        return;
675
    openMovieFromDataGuessType(m_streamReader->pointerToData());
676
}
677
678
MediaSource QuickTimeVideoPlayer::mediaSource() const
679
{
680
    return m_mediaSource;
681
}
682
683
QTMovie *QuickTimeVideoPlayer::qtMovie() const
684
{
685
    return m_QTMovie;
686
}
687
688
void QuickTimeVideoPlayer::setPlaybackRate(float rate)
689
{
690
	PhononAutoReleasePool pool;
691
    m_playbackRateSat = true;
692
    m_playbackRate = rate;
693
    if (m_QTMovie)
694
        [m_QTMovie setRate:m_playbackRate];
695
}
696
697
float QuickTimeVideoPlayer::playbackRate() const
698
{
699
    return m_playbackRate;
700
}
701
702
quint64 QuickTimeVideoPlayer::currentTime() const
703
{
704
    if (!m_QTMovie || m_state == Paused)
705
        return m_currentTime;
706
707
	PhononAutoReleasePool pool;
708
    QTTime qtTime = [m_QTMovie currentTime];
709
    quint64 t = static_cast<quint64>(float(qtTime.timeValue) / float(qtTime.timeScale) * 1000.0f);
710
    const_cast<QuickTimeVideoPlayer *>(this)->m_currentTime = t;
711
    return m_currentTime;
712
}
713
714
long QuickTimeVideoPlayer::timeScale() const
715
{
716
    if (!m_QTMovie)
717
        return 0;
718
719
	PhononAutoReleasePool pool;
720
    return [[m_QTMovie attributeForKey:@"QTMovieTimeScaleAttribute"] longValue];
721
}
722
723
QString QuickTimeVideoPlayer::timeToString(quint64 ms)
724
{
725
    int sec = ms/1000;
726
    int min = sec/60;
727
    int hour = min/60;
728
    return QString(QLatin1String("%1:%2:%3:%4")).arg(hour%60).arg(min%60).arg(sec%60).arg(ms%1000);
729
}
730
731
QString QuickTimeVideoPlayer::currentTimeString()
732
{
733
    return timeToString(currentTime());
734
}
735
736
quint64 QuickTimeVideoPlayer::duration() const
737
{
738
    if (!m_QTMovie)
739
        return 0;
740
741
	PhononAutoReleasePool pool;
742
    QTTime qtTime = [m_QTMovie duration];
743
    return static_cast<quint64>(float(qtTime.timeValue) / float(qtTime.timeScale) * 1000.0f);
744
}
745
746
void QuickTimeVideoPlayer::play()
747
{
748
    if (!canPlayMedia())
749
        return;
750
751
	PhononAutoReleasePool pool;
752
    m_state = Playing;
753
    enableAudio(m_audioEnabled);
754
    setMute(m_mute);
755
    [m_QTMovie setRate:m_playbackRate];
756
}
757
758
void QuickTimeVideoPlayer::pause()
759
{
760
    if (!canPlayMedia())
761
        return;
762
763
	PhononAutoReleasePool pool;
764
    currentTime();
765
    m_state = Paused;
766
767
    if (isSeekable())
768
        [m_QTMovie setRate:0];
769
    else // pretend to be paused:
770
        [m_QTMovie setMuted:0];
771
}
772
773
void QuickTimeVideoPlayer::seek(quint64 milliseconds)
774
{
775
    if (!canPlayMedia() || !isSeekable() || milliseconds == currentTime())
776
        return;
777
    if (milliseconds > duration())
778
        milliseconds = duration();
779
780
	PhononAutoReleasePool pool;
781
    QTTime newQTTime = [m_QTMovie currentTime];
782
    newQTTime.timeValue = (milliseconds / 1000.0f) * newQTTime.timeScale;
783
    [m_QTMovie setCurrentTime:newQTTime];
784
785
    // The movie might not have been able to seek
786
    // to the exact point we told it to. So set
787
    // the current time according to what the movie says:
788
    newQTTime = [m_QTMovie currentTime];
789
    m_currentTime = static_cast<quint64>
790
        (float(newQTTime.timeValue) / float(newQTTime.timeScale) * 1000.0f);
791
792
    if (m_state == Paused){
793
        // We need (for reasons unknown) to task
794
        // the movie twize to make sure that
795
        // a subsequent call to frameAsCvTexture
796
        // returns the correct frame:
797
#ifdef QUICKTIME_C_API_AVAILABLE
798
        MoviesTask(0, 0);
799
        MoviesTask(0, 0);
800
#elif defined(QT_MAC_USE_COCOA)
801
        qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
802
#endif
803
    }
804
}
805
806
bool QuickTimeVideoPlayer::canPlayMedia() const
807
{
808
    if (!m_QTMovie)
809
        return false;
810
    return m_isDrmAuthorized;
811
}
812
813
bool QuickTimeVideoPlayer::isPlaying() const
814
{
815
    return m_state == Playing;
816
}
817
818
bool QuickTimeVideoPlayer::isSeekable() const
819
{
820
    return canPlayMedia() && (duration()-1) != INT_MAX;
821
}
822
823
float QuickTimeVideoPlayer::prefferedPlaybackRate() const
824
{
825
    if (!m_QTMovie)
826
        return 0;
827
828
	PhononAutoReleasePool pool;
829
    return [[m_QTMovie attributeForKey:@"QTMoviePreferredRateAttribute"] floatValue];
830
}
831
832
#ifdef QUICKTIME_C_API_AVAILABLE
833
void MoviePrePrerollCompleteCallBack(Movie /*theMovie*/, OSErr /*thePrerollErr*/, void * /*userData*/)
834
{
835
    // QuickTimeVideoPlayer *player = static_cast<QuickTimeVideoPlayer *>(userData);
836
}
837
#endif
838
839
bool QuickTimeVideoPlayer::preRollMovie(qint64 startTime)
840
{
841
    if (!canPlayMedia())
842
        return false;
843
844
#ifdef QUICKTIME_C_API_AVAILABLE
845
    if (PrePrerollMovie([m_QTMovie quickTimeMovie], startTime, FloatToFixed(m_playbackRate),
846
        0 /*MoviePrePrerollCompleteCallBack*/, this) != noErr) // No callback means wait (synch)
847
        return false;
848
849
    if (PrerollMovie([m_QTMovie quickTimeMovie], startTime, FloatToFixed(m_playbackRate)) != noErr)
850
        return false;
851
852
    return true;
853
#else
854
    Q_UNUSED(startTime);
855
    return false;
856
#endif
857
}
858
859
bool QuickTimeVideoPlayer::hasAudio() const
860
{
861
    if (!m_QTMovie)
862
        return false;
863
864
	PhononAutoReleasePool pool;
865
    return [[m_QTMovie attributeForKey:@"QTMovieHasAudioAttribute"] boolValue] == YES;
866
}
867
868
bool QuickTimeVideoPlayer::hasVideo() const
869
{
870
    return m_hasVideo;
871
}
872
873
bool QuickTimeVideoPlayer::hasMovie() const
874
{
875
    return m_QTMovie != 0;
876
}
877
878
void QuickTimeVideoPlayer::checkIfVideoAwailable()
879
{
880
	PhononAutoReleasePool pool;
881
    m_hasVideo = [[m_QTMovie attributeForKey:@"QTMovieHasVideoAttribute"] boolValue] == YES;
882
}
883
884
bool QuickTimeVideoPlayer::isDrmProtected() const
885
{
886
    return m_isDrmProtected;
887
}
888
889
bool QuickTimeVideoPlayer::isDrmAuthorized() const
890
{
891
    return m_isDrmAuthorized;
892
}
893
/*
894
void QuickTimeVideoPlayer::movieCodecIsMPEG()
895
{
896
    NSArray *tracks = [m_QTMovie tracks];
897
    for (QTTrack *track in tracks)
898
        if ([[track media] hasCharacteristic:QTMediaTypeMPEG])
899
            return true;
900
    return false;
901
}
902
*/
903
904
static void QtGetTrackProtection(QTTrack *track, bool &isDrmProtected, bool &isDrmAuthorized)
905
{
906
    isDrmProtected = false;
907
    isDrmAuthorized = true;
908
909
#ifdef QUICKTIME_C_API_AVAILABLE
910
    QTMedia *media = [track media];
911
    MediaHandler mediaHandler = GetMediaHandler([media quickTimeMedia]);
912
    if (mediaHandler){
913
        // Regardless, skip message boxes pointing to iTunes regarding DRM:
914
        Boolean boolFalse = false;
915
        QTSetComponentProperty(mediaHandler,
916
            kQTPropertyClass_DRM, kQTDRMPropertyID_InteractWithUser,
917
            sizeof(boolFalse), &boolFalse);
918
919
        // Check track:
920
        Boolean value;
921
        OSStatus err = QTGetComponentProperty(mediaHandler,
922
            kQTPropertyClass_DRM, kQTDRMPropertyID_IsProtected,
923
            sizeof(value), &value, 0);
924
        isDrmProtected = (err == noErr) ? bool(value) : false;
925
        err = QTGetComponentProperty(mediaHandler,
926
            kQTPropertyClass_DRM, kQTDRMPropertyID_IsAuthorized,
927
            sizeof(value), &value, 0);
928
        isDrmAuthorized = (err == noErr) ? bool(value) : true;
929
    }
930
#else
931
    Q_UNUSED(track);
932
#endif // QUICKTIME_C_API_AVAILABLE
933
}
934
935
void QuickTimeVideoPlayer::readProtection()
936
{
937
    m_isDrmProtected = false;
938
    m_isDrmAuthorized = true;
939
940
    NSArray *tracks = [m_QTMovie tracks];
941
	for (uint i=0; i<[tracks count]; ++i){
942
		QTTrack *track = [tracks objectAtIndex:i];
943
        bool isDrmProtected = false;
944
        bool isDrmAuthorized = true;
945
        QtGetTrackProtection(track, isDrmProtected, isDrmAuthorized);
946
        if (isDrmProtected)
947
            m_isDrmProtected = true;
948
        if (!isDrmAuthorized)
949
            m_isDrmAuthorized = false;
950
    }
951
}
952
953
}}
954
955
QT_END_NAMESPACE