mirror of
https://review.haiku-os.org/haiku
synced 2025-02-12 08:39:18 +01:00
489 lines
11 KiB
C++
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);
|
|
}
|
|
|
|
|