haiku/src/apps/mediaplayer/VideoView.cpp

489 lines
11 KiB
C++

/*
* Copyright 2006-2010 Stephan Aßmus <superstippi@gmx.de>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#include "VideoView.h"
#include <stdio.h>
#include <Application.h>
#include <Bitmap.h>
#include <Region.h>
#include <Screen.h>
#include <WindowScreen.h>
#include "Settings.h"
#include "SubtitleBitmap.h"
enum {
MSG_INVALIDATE = 'ivdt'
};
VideoView::VideoView(BRect frame, const char* name, uint32 resizeMask)
:
BView(frame, name, resizeMask, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE
| B_PULSE_NEEDED),
fVideoFrame(Bounds()),
fOverlayMode(false),
fIsPlaying(false),
fIsFullscreen(false),
fFullscreenControlsVisible(false),
fFirstPulseAfterFullscreen(false),
fSendHideCounter(0),
fLastMouseMove(system_time()),
fSubtitleBitmap(new SubtitleBitmap),
fSubtitleFrame(),
fSubtitleMaxButtom(Bounds().bottom),
fHasSubtitle(false),
fSubtitleChanged(false),
fGlobalSettingsListener(this)
{
SetViewColor(B_TRANSPARENT_COLOR);
SetHighColor(0, 0, 0);
// create some hopefully sensible default overlay restrictions
// they will be adjusted when overlays are actually used
fOverlayRestrictions.min_width_scale = 0.25;
fOverlayRestrictions.max_width_scale = 8.0;
fOverlayRestrictions.min_height_scale = 0.25;
fOverlayRestrictions.max_height_scale = 8.0;
Settings::Default()->AddListener(&fGlobalSettingsListener);
_AdoptGlobalSettings();
//SetSubTitle("<b><i>This</i></b> is a <font color=\"#00ff00\">test</font>!"
// "\nWith a <i>short</i> line and a <b>long</b> line.");
}
VideoView::~VideoView()
{
Settings::Default()->RemoveListener(&fGlobalSettingsListener);
delete fSubtitleBitmap;
}
void
VideoView::Draw(BRect updateRect)
{
BRegion outSideVideoRegion(updateRect);
if (LockBitmap()) {
if (const BBitmap* bitmap = GetBitmap()) {
outSideVideoRegion.Exclude(fVideoFrame);
if (!fOverlayMode)
_DrawBitmap(bitmap);
else
FillRect(fVideoFrame & updateRect, B_SOLID_LOW);
}
UnlockBitmap();
}
if (outSideVideoRegion.CountRects() > 0)
FillRegion(&outSideVideoRegion);
if (fHasSubtitle)
_DrawSubtitle();
}
void
VideoView::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_OBJECT_CHANGED:
// received from fGlobalSettingsListener
// TODO: find out which object, if we ever watch more than
// the global settings instance...
_AdoptGlobalSettings();
break;
case MSG_INVALIDATE:
{
BRect dirty;
if (message->FindRect("dirty", &dirty) == B_OK)
Invalidate(dirty);
break;
}
default:
BView::MessageReceived(message);
}
}
/*!
Disables the screen saver, and hides the full screen controls.
*/
void
VideoView::Pulse()
{
if (!fIsFullscreen || !fIsPlaying)
return;
bigtime_t now = system_time();
if (now - fLastMouseMove > 1500000) {
fLastMouseMove = now;
BPoint where;
uint32 buttons;
GetMouse(&where, &buttons, false);
if (buttons == 0) {
// Hide the full screen controls (and the mouse pointer)
// after a while
if (fFullscreenControlsVisible || fFirstPulseAfterFullscreen) {
if (fSendHideCounter == 0 || fSendHideCounter == 3) {
// Send after 1.5s and after 4.5s
BMessage message(M_HIDE_FULL_SCREEN_CONTROLS);
message.AddPoint("where", where);
if (fSendHideCounter > 0)
message.AddBool("force", true);
Window()->PostMessage(&message, Window());
}
fSendHideCounter++;
fFirstPulseAfterFullscreen = false;
}
// Take care of disabling the screen saver
ConvertToScreen(&where);
set_mouse_position((int32)where.x, (int32)where.y);
}
}
}
void
VideoView::MouseMoved(BPoint where, uint32 transit,
const BMessage* dragMessage)
{
fLastMouseMove = system_time();
}
// #pragma mark -
void
VideoView::SetBitmap(const BBitmap* bitmap)
{
VideoTarget::SetBitmap(bitmap);
// Attention: Don't lock the window, if the bitmap is NULL. Otherwise
// we're going to deadlock when the window tells the node manager to
// stop the nodes (Window -> NodeManager -> VideoConsumer -> VideoView
// -> Window).
if (!bitmap || LockLooperWithTimeout(10000) != B_OK)
return;
if (LockBitmap()) {
if (fOverlayMode
|| (bitmap->Flags() & B_BITMAP_WILL_OVERLAY) != 0) {
if (!fOverlayMode) {
// init overlay
rgb_color key;
status_t ret = SetViewOverlay(bitmap, bitmap->Bounds(),
fVideoFrame, &key, B_FOLLOW_ALL,
B_OVERLAY_FILTER_HORIZONTAL | B_OVERLAY_FILTER_VERTICAL);
if (ret == B_OK) {
fOverlayKeyColor = key;
SetLowColor(key);
snooze(20000);
FillRect(fVideoFrame, B_SOLID_LOW);
Sync();
// use overlay from here on
_SetOverlayMode(true);
// update restrictions
overlay_restrictions restrictions;
if (bitmap->GetOverlayRestrictions(&restrictions) == B_OK)
fOverlayRestrictions = restrictions;
} else {
// try again next time
// synchronous draw
FillRect(fVideoFrame);
Sync();
}
} else {
// transfer overlay channel
rgb_color key;
SetViewOverlay(bitmap, bitmap->Bounds(), fVideoFrame,
&key, B_FOLLOW_ALL, B_OVERLAY_FILTER_HORIZONTAL
| B_OVERLAY_FILTER_VERTICAL
| B_OVERLAY_TRANSFER_CHANNEL);
}
} else if (fOverlayMode
&& (bitmap->Flags() & B_BITMAP_WILL_OVERLAY) == 0) {
_SetOverlayMode(false);
ClearViewOverlay();
SetViewColor(B_TRANSPARENT_COLOR);
}
if (!fOverlayMode) {
if (fSubtitleChanged) {
_LayoutSubtitle();
Invalidate(fVideoFrame | fSubtitleFrame);
} else if (fHasSubtitle
&& fVideoFrame.Intersects(fSubtitleFrame)) {
Invalidate(fVideoFrame);
} else
_DrawBitmap(bitmap);
}
UnlockBitmap();
}
UnlockLooper();
}
void
VideoView::GetOverlayScaleLimits(float* minScale, float* maxScale) const
{
*minScale = max_c(fOverlayRestrictions.min_width_scale,
fOverlayRestrictions.min_height_scale);
*maxScale = max_c(fOverlayRestrictions.max_width_scale,
fOverlayRestrictions.max_height_scale);
}
void
VideoView::OverlayScreenshotPrepare()
{
// TODO: Do nothing if the current bitmap is in RGB color space
// and no overlay. Otherwise, convert current bitmap to RGB color
// space an draw it in place of the normal display.
}
void
VideoView::OverlayScreenshotCleanup()
{
// TODO: Do nothing if the current bitmap is in RGB color space
// and no overlay. Otherwise clean view area with overlay color.
}
bool
VideoView::UseOverlays() const
{
return fUseOverlays;
}
bool
VideoView::IsOverlayActive()
{
bool active = false;
if (LockBitmap()) {
active = fOverlayMode;
UnlockBitmap();
}
return active;
}
void
VideoView::DisableOverlay()
{
if (!fOverlayMode)
return;
FillRect(Bounds());
Sync();
ClearViewOverlay();
snooze(20000);
Sync();
_SetOverlayMode(false);
}
void
VideoView::SetPlaying(bool playing)
{
fIsPlaying = playing;
}
void
VideoView::SetFullscreen(bool fullScreen)
{
fIsFullscreen = fullScreen;
fSendHideCounter = 0;
fFirstPulseAfterFullscreen = true;
}
void
VideoView::SetFullscreenControlsVisible(bool visible)
{
fFullscreenControlsVisible = visible;
fSendHideCounter = 0;
}
void
VideoView::SetVideoFrame(const BRect& frame)
{
if (fVideoFrame == frame)
return;
BRegion invalid(fVideoFrame | frame);
invalid.Exclude(frame);
Invalidate(&invalid);
fVideoFrame = frame;
fSubtitleBitmap->SetVideoBounds(fVideoFrame.OffsetToCopy(B_ORIGIN));
_LayoutSubtitle();
}
void
VideoView::SetSubTitle(const char* text)
{
BRect oldSubtitleFrame = fSubtitleFrame;
if (text == NULL || text[0] == '\0') {
fHasSubtitle = false;
fSubtitleChanged = true;
} else {
fHasSubtitle = true;
// If the subtitle frame still needs to be invalidated during
// normal playback, make sure we don't unset the fSubtitleChanged
// flag. It will be reset after drawing the subtitle once.
fSubtitleChanged = fSubtitleBitmap->SetText(text) || fSubtitleChanged;
if (fSubtitleChanged)
_LayoutSubtitle();
}
if (!fIsPlaying && Window() != NULL) {
// If we are playing, the new subtitle will be displayed,
// or the old one removed from screen, as soon as the next
// frame is shown. Otherwise we need to invalidate manually.
// But we are not in the window thread and we shall not lock
// it or we may dead-locks.
BMessage message(MSG_INVALIDATE);
message.AddRect("dirty", oldSubtitleFrame | fSubtitleFrame);
Window()->PostMessage(&message);
}
}
void
VideoView::SetSubTitleMaxBottom(float bottom)
{
if (bottom == fSubtitleMaxButtom)
return;
fSubtitleMaxButtom = bottom;
BRect oldSubtitleFrame = fSubtitleFrame;
_LayoutSubtitle();
Invalidate(fSubtitleFrame | oldSubtitleFrame);
}
// #pragma mark -
void
VideoView::_DrawBitmap(const BBitmap* bitmap)
{
SetDrawingMode(B_OP_COPY);
uint32 options = B_WAIT_FOR_RETRACE;
if (fUseBilinearScaling)
options |= B_FILTER_BITMAP_BILINEAR;
DrawBitmapAsync(bitmap, bitmap->Bounds(), fVideoFrame, options);
}
void
VideoView::_DrawSubtitle()
{
SetDrawingMode(B_OP_ALPHA);
SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
DrawBitmapAsync(fSubtitleBitmap->Bitmap(), fSubtitleFrame.LeftTop());
// Unless the subtitle frame intersects the video frame, we don't have
// to draw the subtitle again.
fSubtitleChanged = false;
}
void
VideoView::_AdoptGlobalSettings()
{
mpSettings settings;
Settings::Default()->Get(settings);
fUseOverlays = settings.useOverlays;
fUseBilinearScaling = settings.scaleBilinear;
switch (settings.subtitleSize) {
case mpSettings::SUBTITLE_SIZE_SMALL:
fSubtitleBitmap->SetCharsPerLine(45.0);
break;
case mpSettings::SUBTITLE_SIZE_MEDIUM:
fSubtitleBitmap->SetCharsPerLine(36.0);
break;
case mpSettings::SUBTITLE_SIZE_LARGE:
fSubtitleBitmap->SetCharsPerLine(32.0);
break;
}
fSubtitlePlacement = settings.subtitlePlacement;
_LayoutSubtitle();
Invalidate();
}
void
VideoView::_SetOverlayMode(bool overlayMode)
{
fOverlayMode = overlayMode;
fSubtitleBitmap->SetOverlayMode(overlayMode);
}
void
VideoView::_LayoutSubtitle()
{
if (!fHasSubtitle)
return;
const BBitmap* subtitleBitmap = fSubtitleBitmap->Bitmap();
if (subtitleBitmap == NULL)
return;
fSubtitleFrame = subtitleBitmap->Bounds();
BPoint offset;
offset.x = (fVideoFrame.left + fVideoFrame.right
- fSubtitleFrame.Width()) / 2;
switch (fSubtitlePlacement) {
default:
case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_VIDEO:
offset.y = min_c(fSubtitleMaxButtom, fVideoFrame.bottom)
- fSubtitleFrame.Height();
break;
case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_SCREEN:
{
// Center between video and screen bottom, if there is still
// enough room.
float centeredOffset = (fVideoFrame.bottom + fSubtitleMaxButtom
- fSubtitleFrame.Height()) / 2;
float maxOffset = fSubtitleMaxButtom - fSubtitleFrame.Height();
offset.y = min_c(centeredOffset, maxOffset);
break;
}
}
fSubtitleFrame.OffsetTo(offset);
}