/* This file is part of the KDE project. Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). This library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 2.1 or 3 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #ifdef QT_MAC_USE_COCOA #import #endif #include "videowidget.h" #include "backendheader.h" #include "quicktimevideoplayer.h" #include "medianode.h" #include "medianodeevent.h" #include "mediaobject.h" #include #include #include #include #import #import ///////////////////////////////////////////////////////////////////////////////////////// #ifdef QT_MAC_USE_COCOA // Rendering to a QTMovieView can only be done in Cocoa #define VIDEO_TRANSPARENT(m) -(void)m:(NSEvent *)e{[[self superview] m:e];} @interface SharedQTMovieView : QTMovieView { @private Phonon::QT7::QuickTimeVideoPlayer *m_player; QList *m_parents; QWidget *m_window; QRect *m_drawRect; bool m_newImageReady; bool m_usingWindow; } - (SharedQTMovieView *) init; - (void) registerParent:(QWidget *)parent; - (void) unregisterParent:(QWidget *)parent; - (void) setDrawRect:(QRect &)rect; - (void) drawVideoFrame:(Phonon::QT7::VideoFrame &)frame forWidget:(QWidget *)widget shareImages:(bool)share; - (void) useOffscreenWindow:(bool)offscreen; - (void) applyDrawRectOnSelf; @end ///////////////////////////////////////////////////////////////////////////////////////// @implementation SharedQTMovieView - (SharedQTMovieView *) init { self = [super initWithFrame:NSZeroRect]; if (self){ [self setControllerVisible:NO]; m_parents = new QList(); m_drawRect = new QRect(0, 0, 1, 1); [self applyDrawRectOnSelf]; m_usingWindow = false; } return self; } - (void) dealloc { Phonon::QT7::PhononAutoReleasePool pool; delete m_window; delete m_drawRect; delete m_parents; [super dealloc]; } - (void) applyDrawRectOnSelf { NSRect nsrect; nsrect.origin.x = m_drawRect->x(); nsrect.origin.y = m_drawRect->y(); nsrect.size.width = m_drawRect->width(); nsrect.size.height = m_drawRect->height(); [self setFrame:nsrect]; } - (void) setDrawRect:(QRect &)rect { *m_drawRect = rect; if (!m_usingWindow) [self applyDrawRectOnSelf]; } - (void) waitForFrame { if (m_usingWindow){ QTMovie *movie = [self movie]; if (movie){ // CIImages are expected, but not received. // Try to wait a couple of seconds for them: m_newImageReady = false; float rate = [movie rate]; if (!rate) [movie setRate:1]; QTime t; t.start(); while (!m_newImageReady && t.elapsed() < 2000) ; [movie setRate:rate]; } } } - (void) useOffscreenWindow:(bool)offscreen { if (offscreen == m_usingWindow) return; if (offscreen){ if (!m_window){ m_window = new QWidget; m_window->setWindowOpacity(0.0); m_window->show(); m_window->hide(); } m_usingWindow = true; [self setDelegate:self]; [self waitForFrame]; foreach(QWidget *w, *m_parents) w->repaint(); qApp->processEvents(); [self removeFromSuperview]; [(NSView *)m_window->winId() addSubview:self]; } else if (!m_parents->isEmpty()){ m_usingWindow = false; [self removeFromSuperview]; [(NSView*)m_parents->first()->winId() addSubview:self]; [self setDelegate:0]; [self setDrawRect:*m_drawRect]; } } - (void) drawVideoFrame:(Phonon::QT7::VideoFrame &)frame forWidget:(QWidget *)widget shareImages:(bool)share; { // Detect if the video that produces the frame has changed: Phonon::QT7::QuickTimeVideoPlayer *player = frame.videoPlayer(); if (player && player->qtMovie() != [self movie]){ m_player = player; [self setMovie:player->qtMovie()]; [self waitForFrame]; } [self useOffscreenWindow:(share || m_parents->size() > 1)]; if (m_usingWindow) widget->update(); } // Override this method so that the movie doesn't stop if // the window becomes invisible - (void)viewWillMoveToWindow:(NSWindow *)newWindow { Q_UNUSED(newWindow); } - (CIImage *) view:(QTMovieView *)view willDisplayImage:(CIImage *)img { // This method is called from QTMovieView just // before the image will be drawn. Q_UNUSED(view); m_player->setPrimaryRenderingCIImage(img); m_newImageReady = true; return img; } - (void) registerParent:(QWidget *)parent { if (m_parents->contains(parent)) return; m_parents->append(parent); if (m_parents->size() == 1){ Phonon::QT7::PhononAutoReleasePool pool; m_usingWindow = true; [self applyDrawRectOnSelf]; [self useOffscreenWindow:NO]; } } - (void) unregisterParent:(QWidget *)parent { m_parents->removeAll(parent); if (m_parents->size() == 1) [self applyDrawRectOnSelf]; } VIDEO_TRANSPARENT(mouseDown); VIDEO_TRANSPARENT(mouseDragged); VIDEO_TRANSPARENT(mouseUp); VIDEO_TRANSPARENT(mouseMoved); VIDEO_TRANSPARENT(mouseEntered); VIDEO_TRANSPARENT(mouseExited); VIDEO_TRANSPARENT(rightMouseDown); VIDEO_TRANSPARENT(rightMouseDragged); VIDEO_TRANSPARENT(rightMouseUp); VIDEO_TRANSPARENT(otherMouseDown); VIDEO_TRANSPARENT(otherMouseDragged); VIDEO_TRANSPARENT(otherMouseUp); VIDEO_TRANSPARENT(keyDown); VIDEO_TRANSPARENT(keyUp); VIDEO_TRANSPARENT(scrollWheel) @end #endif // QT_MAC_USE_COCOA ///////////////////////////////////////////////////////////////////////////////////////// QT_BEGIN_NAMESPACE namespace Phonon { namespace QT7 { class IVideoRenderDrawWidget { public: virtual ~IVideoRenderDrawWidget(){} virtual void setVideoFrame(VideoFrame &) = 0; virtual void setDrawFrameRect(const QRect &) = 0; virtual void updateVideoOutputCount(int){} virtual void setMovieIsPaused(bool){} }; ///////////////////////////////////////////////////////////////////////////////////////// QGLWidget *PhononSharedQGLWidget(){ static QGLWidget *glWidget = 0; if (!glWidget) glWidget = new QGLWidget(); return glWidget; } ///////////////////////////////////////////////////////////////////////////////////////// class RenderOpenGL : public QGLWidget, public IVideoRenderDrawWidget { public: VideoFrame m_currentFrame; QRect m_drawFrameRect; RenderOpenGL(QWidget *parent, const QGLFormat &format, const QSize &size) : QGLWidget(format, parent, PhononSharedQGLWidget()) { resize(size); setAutoFillBackground(false); show(); } void initializeGL() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); } void resizeGL(int w, int h) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glViewport(0, 0, GLsizei(w), GLsizei(h)); glOrtho(0, GLsizei(w), 0, GLsizei(h), -1, 1); updateGL(); } void paintGL() { glClear(GL_COLOR_BUFFER_BIT); m_currentFrame.drawCVTexture(m_drawFrameRect); } void setVideoFrame(VideoFrame &frame) { m_currentFrame = frame; makeCurrent(); paintGL(); swapBuffers(); } void setDrawFrameRect(const QRect &rect) { m_drawFrameRect = rect; } }; ///////////////////////////////////////////////////////////////////////////////////////// class RenderQTMovieView : public QWidget, public IVideoRenderDrawWidget { public: #if defined(QT_MAC_USE_COCOA) QRect m_drawRect; VideoFrame m_videoFrame; SharedQTMovieView *m_currentView; bool m_setDrawRectPending; bool m_share; RenderQTMovieView(bool share, QWidget *parent, const QSize &size=QSize()) : QWidget(parent), m_currentView(0) { m_setDrawRectPending = true; m_share = share; setAutoFillBackground(false); if (share){ // In 'share' mode, this widget will only make sure // that CIIImages are produced, and not actually // draw anything: hide(); } else { resize(size); show(); } } ~RenderQTMovieView() { [m_currentView unregisterParent:this]; } void showEvent(QShowEvent *) { if (m_share) return; [m_currentView registerParent:this]; } void hideEvent(QHideEvent *) { if (m_share) return; [m_currentView unregisterParent:this]; } void paintEvent(QPaintEvent *) { if (m_share) return; QPainter p(this); p.fillRect(rect(), Qt::black); m_videoFrame.drawCIImage(m_drawRect); } void updateVideoOutputCount(int count) { Q_UNUSED(count); } void setMovieIsPaused(bool paused) { Q_UNUSED(paused); } void setVideoFrame(VideoFrame &frame) { m_videoFrame = frame; if (!m_videoFrame.isEmpty()){ Phonon::QT7::QuickTimeVideoPlayer *player = m_videoFrame.videoPlayer(); if (!player->m_primaryRenderingTarget){ // First movie view. Create the shared resource: SharedQTMovieView *view = [[[SharedQTMovieView alloc] init] autorelease]; player->setPrimaryRenderingTarget(view); } SharedQTMovieView *view = static_cast(player->m_primaryRenderingTarget); if (!m_share && view != m_currentView){ [m_currentView unregisterParent:this]; m_currentView = view; [m_currentView registerParent:this]; } [view drawVideoFrame:m_videoFrame forWidget:this shareImages:m_share || m_videoFrame.hasColorAdjustments()]; if (m_setDrawRectPending){ m_setDrawRectPending = false; [view setDrawRect:m_drawRect]; } } } void setDrawFrameRect(const QRect &rect) { m_drawRect = rect; Phonon::QT7::QuickTimeVideoPlayer *player = m_videoFrame.videoPlayer(); if (player && player->m_primaryRenderingTarget){ SharedQTMovieView *view = static_cast(player->m_primaryRenderingTarget); [view setDrawRect:m_drawRect]; } else m_setDrawRectPending = true; } #else // QT_MAC_USE_COCOA == false RenderQTMovieView(bool, QWidget *, const QSize& = QSize()){} void setVideoFrame(VideoFrame &){} void setDrawFrameRect(const QRect &){} #endif }; ///////////////////////////////////////////////////////////////////////////////////////// class RenderQTMovieLayer : public QWidget, public IVideoRenderDrawWidget { public: #ifdef QT_MAC_USE_COCOA QTMovieLayer *m_movieLayer; RenderQTMovieLayer(QWidget *parent, const QSize&) : QWidget(parent) { PhononAutoReleasePool pool; setAutoFillBackground(false); m_movieLayer = 0; [(NSView *)winId() setWantsLayer:YES]; } void setVideoFrame(VideoFrame &frame) { QuickTimeVideoPlayer *player = frame.videoPlayer(); if (!player || player->qtMovie() == [m_movieLayer movie]) return; if (m_movieLayer) [m_movieLayer setMovie:player->qtMovie()]; else { m_movieLayer = [QTMovieLayer layerWithMovie:player->qtMovie()]; [(NSView *)winId() setLayer:m_movieLayer]; } } void setDrawFrameRect(const QRect &rect) { CGRect frame = m_movieLayer.frame; frame.origin.x = rect.x(); frame.origin.y = rect.y(); frame.size.width = rect.width(); frame.size.height = rect.height(); m_movieLayer.frame = frame; } #else // QT_MAC_USE_COCOA == false RenderQTMovieLayer(QWidget *, const QSize&){} void setVideoFrame(VideoFrame &){} void setDrawFrameRect(const QRect &){} #endif }; ///////////////////////////////////////////////////////////////////////////////////////// class VideoRenderWidget : public QWidget { public: enum RenderSystem { RS_NoRendering = 0, RS_QGLWidget = 1, RS_QPainter = 2, RS_CIImage = 3, RS_CVTexture = 4, RS_QImage = 5, RS_QTMovieView = 6, RS_QTMovieLayer = 7 } m_renderSystem; VideoFrame m_currentFrame; QRect m_movieFrameRect; QRect m_drawFrameRect; Phonon::VideoWidget::ScaleMode m_scaleMode; Phonon::VideoWidget::AspectRatio m_aspect; IVideoRenderDrawWidget *m_renderDrawWidget; qreal m_brightness; qreal m_contrast; qreal m_hue; qreal m_saturation; qreal m_opacity; VideoRenderWidget() : QWidget(0), m_scaleMode(Phonon::VideoWidget::FitInView), m_aspect(Phonon::VideoWidget::AspectRatioAuto) { PhononAutoReleasePool pool; m_brightness = 0; m_contrast = 0; m_hue = 0; m_saturation = 0; m_opacity = 1; m_renderDrawWidget = 0; m_renderSystem = RS_NoRendering; setAutoFillBackground(false); updateDrawFrameRect(); } RenderSystem selectBestRenderSystem(){ if (!isVisible()) return RS_NoRendering; else if (window() && window()->testAttribute(Qt::WA_DontShowOnScreen)) return RS_QPainter; else { #ifdef QUICKTIME_C_API_AVAILABLE return RS_QGLWidget; #else return RS_QTMovieView; #endif } } void setRenderSystem(RenderSystem renderSystem){ PhononAutoReleasePool pool; static QString userSystem = qgetenv("PHONON_RENDER_SYSTEM"); if (!userSystem.isEmpty()) renderSystem = RenderSystem(userSystem.toInt()); if (m_renderSystem == renderSystem) return; m_renderSystem = renderSystem; if (m_renderDrawWidget){ delete m_renderDrawWidget; m_renderDrawWidget = 0; } switch (m_renderSystem){ case RS_QGLWidget:{ QGLFormat format = QGLFormat::defaultFormat(); format.setSwapInterval(1); // Vertical sync (avoid tearing) m_renderDrawWidget = new RenderOpenGL(this, format, size()); break;} case RS_QTMovieView:{ m_renderDrawWidget = new RenderQTMovieView(false, this, size()); break;} case RS_QTMovieLayer:{ m_renderDrawWidget = new RenderQTMovieLayer(this, size()); break;} case RS_QPainter: case RS_CIImage: case RS_CVTexture: case RS_QImage: #ifndef QUICKTIME_C_API_AVAILABLE // On cocoa-64, let QTMovieView produce // video frames for us: m_renderDrawWidget = new RenderQTMovieView(true, this); #endif break; case RS_NoRendering: break; } if (m_renderDrawWidget){ m_renderDrawWidget->setVideoFrame(m_currentFrame); m_renderDrawWidget->setDrawFrameRect(m_drawFrameRect); } } QSize sizeHint() const { return m_movieFrameRect.size(); } bool event(QEvent *event) { switch (event->type()){ // Try to detect if one of this objects // anchestors might have changed: case QEvent::Resize:{ PhononAutoReleasePool pool; updateDrawFrameRect(); if (m_renderDrawWidget) dynamic_cast(m_renderDrawWidget)->resize(size()); break; } case QEvent::Paint:{ PhononAutoReleasePool pool; float opacity = parentWidget() ? parentWidget()->windowOpacity() : 1; switch (m_renderSystem){ case RS_QPainter:{ QPainter p(this); p.fillRect(rect(), Qt::black); if (p.paintEngine()->type() == QPaintEngine::OpenGL) m_currentFrame.drawCVTexture(m_drawFrameRect, opacity); else m_currentFrame.drawQImage(&p, m_drawFrameRect); break; } case RS_CIImage: m_currentFrame.drawCIImage(m_drawFrameRect, opacity); break; case RS_CVTexture: m_currentFrame.drawCVTexture(m_drawFrameRect, opacity); break; case RS_QImage:{ QPainter p(this); p.fillRect(rect(), Qt::black); m_currentFrame.drawQImage(&p, m_drawFrameRect); break; } case RS_QGLWidget: case RS_QTMovieView: case RS_QTMovieLayer: // draw in separate widget break; case RS_NoRendering: QPainter p(this); p.fillRect(rect(), Qt::black); break; } break; } default: break; } return QWidget::event(event); } void setVideoFrame(VideoFrame &frame) { PhononAutoReleasePool pool; m_currentFrame = frame; m_currentFrame.setColors(m_brightness, m_contrast, m_hue, m_saturation); if (m_renderDrawWidget) m_renderDrawWidget->setVideoFrame(m_currentFrame); setRenderSystem(selectBestRenderSystem()); switch (m_renderSystem){ case RS_QGLWidget: case RS_QTMovieView: case RS_QTMovieLayer: case RS_NoRendering: break; case RS_CIImage: case RS_CVTexture: case RS_QImage: case RS_QPainter: repaint(); break; } } void updateVideoFrame() { setVideoFrame(m_currentFrame); } void setMovieRect(const QRect &mrect) { if (mrect == m_movieFrameRect) return; m_movieFrameRect = mrect; updateDrawFrameRect(); updateGeometry(); if (isVisible()) qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers); } void setScaleMode(Phonon::VideoWidget::ScaleMode scaleMode) { m_scaleMode = scaleMode; updateDrawFrameRect(); updateVideoFrame(); repaint(); } void setAspectRatio(Phonon::VideoWidget::AspectRatio aspect) { m_aspect = aspect; updateDrawFrameRect(); updateVideoFrame(); repaint(); } void updateVideoOutputCount(int count) { if (m_renderDrawWidget) m_renderDrawWidget->updateVideoOutputCount(count); } void setMovieIsPaused(bool paused) { if (m_renderDrawWidget) m_renderDrawWidget->setMovieIsPaused(paused); } void updateDrawFrameRect() { if (m_movieFrameRect.width() <= 0 || m_movieFrameRect.height() <= 0) m_movieFrameRect = QRect(0, 0, 640, 480); // Set m_drawFrameRect to be the size of the smallest possible // rect conforming to the aspect and containing the whole frame: switch(m_aspect){ case Phonon::VideoWidget::AspectRatioWidget: m_drawFrameRect = rect(); break; case Phonon::VideoWidget::AspectRatio4_3: m_drawFrameRect = scaleToAspect(m_movieFrameRect, 4, 3); break; case Phonon::VideoWidget::AspectRatio16_9: m_drawFrameRect = scaleToAspect(m_movieFrameRect, 16, 9); break; case Phonon::VideoWidget::AspectRatioAuto: default: m_drawFrameRect = m_movieFrameRect; break; } // Scale m_drawFrameRect to fill the widget // without breaking aspect: int widgetWidth = rect().width(); int widgetHeight = rect().height(); int frameWidth = widgetWidth; int frameHeight = m_drawFrameRect.height() * float(widgetWidth) / float(m_drawFrameRect.width()); switch(m_scaleMode){ case Phonon::VideoWidget::ScaleAndCrop: if (frameHeight < widgetHeight){ frameWidth *= float(widgetHeight) / float(frameHeight); frameHeight = widgetHeight; } break; case Phonon::VideoWidget::FitInView: default: if (frameHeight > widgetHeight){ frameWidth *= float(widgetHeight) / float(frameHeight); frameHeight = widgetHeight; } break; } m_drawFrameRect.setSize(QSize(frameWidth, frameHeight)); m_drawFrameRect.moveTo((widgetWidth - frameWidth) / 2.0f, (widgetHeight - frameHeight) / 2.0f); if (m_renderDrawWidget) m_renderDrawWidget->setDrawFrameRect(m_drawFrameRect); } QRect scaleToAspect(QRect srcRect, int w, int h) { int width = srcRect.width(); int height = srcRect.width() * (float(h) / float(w)); if (height > srcRect.height()){ height = srcRect.height(); width = srcRect.height() * (float(w) / float(h)); } return QRect(0, 0, width, height); } }; ///////////////////////////////////////////////////////////////////////////////////////// VideoWidget::VideoWidget(QObject *parent) : MediaNode(VideoSink, parent) { m_videoRenderWidget = new VideoRenderWidget(); } VideoWidget::~VideoWidget() { delete m_videoRenderWidget; } QWidget *VideoWidget::widget() { IMPLEMENTED; return m_videoRenderWidget; } Phonon::VideoWidget::AspectRatio VideoWidget::aspectRatio() const { IMPLEMENTED; return m_videoRenderWidget->m_aspect; } void VideoWidget::setAspectRatio(Phonon::VideoWidget::AspectRatio aspect) { IMPLEMENTED; m_videoRenderWidget->setAspectRatio(aspect); } Phonon::VideoWidget::ScaleMode VideoWidget::scaleMode() const { IMPLEMENTED; return m_videoRenderWidget->m_scaleMode; } void VideoWidget::setScaleMode(Phonon::VideoWidget::ScaleMode scaleMode) { IMPLEMENTED; m_videoRenderWidget->setScaleMode(scaleMode); } qreal VideoWidget::brightness() const { IMPLEMENTED; return m_videoRenderWidget->m_brightness; } void VideoWidget::setBrightness(qreal value) { IMPLEMENTED; m_videoRenderWidget->m_brightness = value; if (m_owningMediaObject && m_owningMediaObject->state() == Phonon::PausedState) m_videoRenderWidget->updateVideoFrame(); } qreal VideoWidget::contrast() const { IMPLEMENTED; return m_videoRenderWidget->m_contrast; } void VideoWidget::setContrast(qreal value) { IMPLEMENTED; m_videoRenderWidget->m_contrast = value; if (m_owningMediaObject && m_owningMediaObject->state() == Phonon::PausedState) m_videoRenderWidget->updateVideoFrame(); } qreal VideoWidget::hue() const { IMPLEMENTED; return m_videoRenderWidget->m_hue; } void VideoWidget::setHue(qreal value) { IMPLEMENTED; m_videoRenderWidget->m_hue = value; if (m_owningMediaObject && m_owningMediaObject->state() == Phonon::PausedState) m_videoRenderWidget->updateVideoFrame(); } qreal VideoWidget::saturation() const { IMPLEMENTED; return m_videoRenderWidget->m_saturation; } void VideoWidget::setSaturation(qreal value) { IMPLEMENTED; m_videoRenderWidget->m_saturation = value; if (m_owningMediaObject && m_owningMediaObject->state() == Phonon::PausedState) m_videoRenderWidget->updateVideoFrame(); } void VideoWidget::mediaNodeEvent(const MediaNodeEvent *event) { switch (event->type()){ case MediaNodeEvent::VideoFrameSizeChanged: m_videoRenderWidget->setMovieRect(*static_cast(event->data())); break; case MediaNodeEvent::VideoOutputCountChanged: m_videoRenderWidget->updateVideoOutputCount(*static_cast(event->data())); break; case MediaNodeEvent::MediaPlaying: m_videoRenderWidget->setMovieIsPaused(!(*static_cast(event->data()))); break; default: break; } } void VideoWidget::updateVideo(VideoFrame &frame){ PhononAutoReleasePool pool; m_videoRenderWidget->setVideoFrame(frame); MediaNode::updateVideo(frame); } }} // namespace Phonon::QT7 QT_END_NAMESPACE #include "moc_videowidget.cpp"