1 /* 2 * Copyright 2006-2010 Stephan Aßmus <superstippi@gmx.de> 3 * All rights reserved. Distributed under the terms of the MIT license. 4 */ 5 6 7 #include "VideoView.h" 8 9 #include <stdio.h> 10 11 #include <Application.h> 12 #include <Bitmap.h> 13 #include <Region.h> 14 #include <Screen.h> 15 #include <WindowScreen.h> 16 17 #include "Settings.h" 18 #include "SubtitleBitmap.h" 19 20 21 enum { 22 MSG_INVALIDATE = 'ivdt' 23 }; 24 25 26 VideoView::VideoView(BRect frame, const char* name, uint32 resizeMask) 27 : 28 BView(frame, name, resizeMask, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE 29 | B_PULSE_NEEDED), 30 fVideoFrame(Bounds()), 31 fOverlayMode(false), 32 fIsPlaying(false), 33 fIsFullscreen(false), 34 fLastMouseMove(system_time()), 35 36 fSubtitleBitmap(new SubtitleBitmap), 37 fSubtitleFrame(), 38 fSubtitleMaxButtom(Bounds().bottom), 39 fHasSubtitle(false), 40 fSubtitleChanged(false), 41 42 fGlobalSettingsListener(this) 43 { 44 SetViewColor(B_TRANSPARENT_COLOR); 45 SetHighColor(0, 0, 0); 46 47 // create some hopefully sensible default overlay restrictions 48 // they will be adjusted when overlays are actually used 49 fOverlayRestrictions.min_width_scale = 0.25; 50 fOverlayRestrictions.max_width_scale = 8.0; 51 fOverlayRestrictions.min_height_scale = 0.25; 52 fOverlayRestrictions.max_height_scale = 8.0; 53 54 Settings::Default()->AddListener(&fGlobalSettingsListener); 55 _AdoptGlobalSettings(); 56 57 //SetSubTitle("<b><i>This</i></b> is a <font color=\"#00ff00\">test</font>!" 58 // "\nWith a <i>short</i> line and a <b>long</b> line."); 59 } 60 61 62 VideoView::~VideoView() 63 { 64 Settings::Default()->RemoveListener(&fGlobalSettingsListener); 65 delete fSubtitleBitmap; 66 } 67 68 69 void 70 VideoView::Draw(BRect updateRect) 71 { 72 BRegion outSideVideoRegion(updateRect); 73 74 if (LockBitmap()) { 75 if (const BBitmap* bitmap = GetBitmap()) { 76 outSideVideoRegion.Exclude(fVideoFrame); 77 if (!fOverlayMode) 78 _DrawBitmap(bitmap); 79 else 80 FillRect(fVideoFrame & updateRect, B_SOLID_LOW); 81 } 82 UnlockBitmap(); 83 } 84 85 if (outSideVideoRegion.CountRects() > 0) 86 FillRegion(&outSideVideoRegion); 87 88 if (fHasSubtitle) 89 _DrawSubtitle(); 90 } 91 92 93 void 94 VideoView::MessageReceived(BMessage* message) 95 { 96 switch (message->what) { 97 case MSG_OBJECT_CHANGED: 98 // received from fGlobalSettingsListener 99 // TODO: find out which object, if we ever watch more than 100 // the global settings instance... 101 _AdoptGlobalSettings(); 102 break; 103 case MSG_INVALIDATE: 104 { 105 BRect dirty; 106 if (message->FindRect("dirty", &dirty) == B_OK) 107 Invalidate(dirty); 108 break; 109 } 110 default: 111 BView::MessageReceived(message); 112 } 113 } 114 115 116 void 117 VideoView::Pulse() 118 { 119 if (!fIsFullscreen || !fIsPlaying) 120 return; 121 122 bigtime_t now = system_time(); 123 if (now - fLastMouseMove > 1500000) { 124 fLastMouseMove = now; 125 // take care of disabling the screen saver 126 BPoint where; 127 uint32 buttons; 128 GetMouse(&where, &buttons, false); 129 if (buttons == 0) { 130 BMessage message(M_HIDE_FULL_SCREEN_CONTROLS); 131 message.AddPoint("where", where); 132 Window()->PostMessage(&message, Window()); 133 134 ConvertToScreen(&where); 135 set_mouse_position((int32)where.x, (int32)where.y); 136 } 137 } 138 } 139 140 141 void 142 VideoView::MouseMoved(BPoint where, uint32 transit, 143 const BMessage* dragMessage) 144 { 145 fLastMouseMove = system_time(); 146 } 147 148 149 // #pragma mark - 150 151 152 void 153 VideoView::SetBitmap(const BBitmap* bitmap) 154 { 155 VideoTarget::SetBitmap(bitmap); 156 // Attention: Don't lock the window, if the bitmap is NULL. Otherwise 157 // we're going to deadlock when the window tells the node manager to 158 // stop the nodes (Window -> NodeManager -> VideoConsumer -> VideoView 159 // -> Window). 160 if (!bitmap || LockLooperWithTimeout(10000) != B_OK) 161 return; 162 163 if (LockBitmap()) { 164 if (fOverlayMode 165 || (bitmap->Flags() & B_BITMAP_WILL_OVERLAY) != 0) { 166 if (!fOverlayMode) { 167 // init overlay 168 rgb_color key; 169 status_t ret = SetViewOverlay(bitmap, bitmap->Bounds(), 170 fVideoFrame, &key, B_FOLLOW_ALL, 171 B_OVERLAY_FILTER_HORIZONTAL 172 | B_OVERLAY_FILTER_VERTICAL); 173 if (ret == B_OK) { 174 fOverlayKeyColor = key; 175 SetLowColor(key); 176 snooze(20000); 177 FillRect(fVideoFrame, B_SOLID_LOW); 178 Sync(); 179 // use overlay from here on 180 _SetOverlayMode(true); 181 182 // update restrictions 183 overlay_restrictions restrictions; 184 if (bitmap->GetOverlayRestrictions(&restrictions) 185 == B_OK) 186 fOverlayRestrictions = restrictions; 187 } else { 188 // try again next time 189 // synchronous draw 190 FillRect(fVideoFrame); 191 Sync(); 192 } 193 } else { 194 // transfer overlay channel 195 rgb_color key; 196 SetViewOverlay(bitmap, bitmap->Bounds(), fVideoFrame, 197 &key, B_FOLLOW_ALL, B_OVERLAY_FILTER_HORIZONTAL 198 | B_OVERLAY_FILTER_VERTICAL 199 | B_OVERLAY_TRANSFER_CHANNEL); 200 } 201 } else if (fOverlayMode 202 && (bitmap->Flags() & B_BITMAP_WILL_OVERLAY) == 0) { 203 _SetOverlayMode(false); 204 ClearViewOverlay(); 205 SetViewColor(B_TRANSPARENT_COLOR); 206 } 207 if (!fOverlayMode) { 208 if (fSubtitleChanged) { 209 _LayoutSubtitle(); 210 Invalidate(fVideoFrame | fSubtitleFrame); 211 } else if (fHasSubtitle 212 && fVideoFrame.Intersects(fSubtitleFrame)) { 213 Invalidate(fVideoFrame); 214 } else 215 _DrawBitmap(bitmap); 216 } 217 218 UnlockBitmap(); 219 } 220 UnlockLooper(); 221 } 222 223 224 void 225 VideoView::GetOverlayScaleLimits(float* minScale, float* maxScale) const 226 { 227 *minScale = max_c(fOverlayRestrictions.min_width_scale, 228 fOverlayRestrictions.min_height_scale); 229 *maxScale = max_c(fOverlayRestrictions.max_width_scale, 230 fOverlayRestrictions.max_height_scale); 231 } 232 233 234 void 235 VideoView::OverlayScreenshotPrepare() 236 { 237 // TODO: Do nothing if the current bitmap is in RGB color space 238 // and no overlay. Otherwise, convert current bitmap to RGB color 239 // space an draw it in place of the normal display. 240 } 241 242 243 void 244 VideoView::OverlayScreenshotCleanup() 245 { 246 // TODO: Do nothing if the current bitmap is in RGB color space 247 // and no overlay. Otherwise clean view area with overlay color. 248 } 249 250 251 bool 252 VideoView::UseOverlays() const 253 { 254 return fUseOverlays; 255 } 256 257 258 bool 259 VideoView::IsOverlayActive() 260 { 261 bool active = false; 262 if (LockBitmap()) { 263 active = fOverlayMode; 264 UnlockBitmap(); 265 } 266 return active; 267 } 268 269 270 void 271 VideoView::DisableOverlay() 272 { 273 if (!fOverlayMode) 274 return; 275 276 FillRect(Bounds()); 277 Sync(); 278 279 ClearViewOverlay(); 280 snooze(20000); 281 Sync(); 282 _SetOverlayMode(false); 283 } 284 285 286 void 287 VideoView::SetPlaying(bool playing) 288 { 289 fIsPlaying = playing; 290 } 291 292 293 void 294 VideoView::SetFullscreen(bool fullScreen) 295 { 296 fIsFullscreen = fullScreen; 297 } 298 299 300 void 301 VideoView::SetVideoFrame(const BRect& frame) 302 { 303 if (fVideoFrame == frame) 304 return; 305 306 BRegion invalid(fVideoFrame | frame); 307 invalid.Exclude(frame); 308 Invalidate(&invalid); 309 310 fVideoFrame = frame; 311 312 fSubtitleBitmap->SetVideoBounds(fVideoFrame.OffsetToCopy(B_ORIGIN)); 313 _LayoutSubtitle(); 314 } 315 316 317 void 318 VideoView::SetSubTitle(const char* text) 319 { 320 BRect oldSubtitleFrame = fSubtitleFrame; 321 322 if (text == NULL || text[0] == '\0') { 323 fHasSubtitle = false; 324 fSubtitleChanged = true; 325 } else { 326 fHasSubtitle = true; 327 // If the subtitle frame still needs to be invalidated during 328 // normal playback, make sure we don't unset the fSubtitleChanged 329 // flag. It will be reset after drawing the subtitle once. 330 fSubtitleChanged = fSubtitleBitmap->SetText(text) || fSubtitleChanged; 331 if (fSubtitleChanged) 332 _LayoutSubtitle(); 333 } 334 335 if (!fIsPlaying && Window() != NULL) { 336 // If we are playing, the new subtitle will be displayed, 337 // or the old one removed from screen, as soon as the next 338 // frame is shown. Otherwise we need to invalidate manually. 339 // But we are not in the window thread and we shall not lock 340 // it or we may dead-locks. 341 BMessage message(MSG_INVALIDATE); 342 message.AddRect("dirty", oldSubtitleFrame | fSubtitleFrame); 343 Window()->PostMessage(&message); 344 } 345 } 346 347 348 void 349 VideoView::SetSubTitleMaxBottom(float bottom) 350 { 351 if (bottom == fSubtitleMaxButtom) 352 return; 353 354 fSubtitleMaxButtom = bottom; 355 356 BRect oldSubtitleFrame = fSubtitleFrame; 357 _LayoutSubtitle(); 358 Invalidate(fSubtitleFrame | oldSubtitleFrame); 359 } 360 361 362 // #pragma mark - 363 364 365 void 366 VideoView::_DrawBitmap(const BBitmap* bitmap) 367 { 368 SetDrawingMode(B_OP_COPY); 369 uint32 options = B_WAIT_FOR_RETRACE; 370 if (fUseBilinearScaling) 371 options |= B_FILTER_BITMAP_BILINEAR; 372 373 DrawBitmapAsync(bitmap, bitmap->Bounds(), fVideoFrame, options); 374 } 375 376 377 void 378 VideoView::_DrawSubtitle() 379 { 380 SetDrawingMode(B_OP_ALPHA); 381 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 382 383 DrawBitmapAsync(fSubtitleBitmap->Bitmap(), fSubtitleFrame.LeftTop()); 384 385 // Unless the subtitle frame intersects the video frame, we don't have 386 // to draw the subtitle again. 387 fSubtitleChanged = false; 388 } 389 390 391 void 392 VideoView::_AdoptGlobalSettings() 393 { 394 mpSettings settings = Settings::CurrentSettings(); 395 // thread safe 396 397 fUseOverlays = settings.useOverlays; 398 fUseBilinearScaling = settings.scaleBilinear; 399 400 switch (settings.subtitleSize) { 401 case mpSettings::SUBTITLE_SIZE_SMALL: 402 fSubtitleBitmap->SetCharsPerLine(45.0); 403 break; 404 case mpSettings::SUBTITLE_SIZE_MEDIUM: 405 fSubtitleBitmap->SetCharsPerLine(36.0); 406 break; 407 case mpSettings::SUBTITLE_SIZE_LARGE: 408 fSubtitleBitmap->SetCharsPerLine(32.0); 409 break; 410 } 411 412 fSubtitlePlacement = settings.subtitlePlacement; 413 414 _LayoutSubtitle(); 415 Invalidate(); 416 } 417 418 419 void 420 VideoView::_SetOverlayMode(bool overlayMode) 421 { 422 fOverlayMode = overlayMode; 423 fSubtitleBitmap->SetOverlayMode(overlayMode); 424 } 425 426 427 void 428 VideoView::_LayoutSubtitle() 429 { 430 if (!fHasSubtitle) 431 return; 432 433 const BBitmap* subtitleBitmap = fSubtitleBitmap->Bitmap(); 434 if (subtitleBitmap == NULL) 435 return; 436 437 fSubtitleFrame = subtitleBitmap->Bounds(); 438 439 BPoint offset; 440 offset.x = (fVideoFrame.left + fVideoFrame.right 441 - fSubtitleFrame.Width()) / 2; 442 switch (fSubtitlePlacement) { 443 default: 444 case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_VIDEO: 445 offset.y = fVideoFrame.bottom - fSubtitleFrame.Height(); 446 break; 447 case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_SCREEN: 448 { 449 // Center between video and screen bottom, if there is still 450 // enough room. 451 float centeredOffset = (fVideoFrame.bottom + fSubtitleMaxButtom 452 - fSubtitleFrame.Height()) / 2; 453 float maxOffset = fSubtitleMaxButtom - fSubtitleFrame.Height(); 454 offset.y = min_c(centeredOffset, maxOffset); 455 break; 456 } 457 } 458 459 fSubtitleFrame.OffsetTo(offset); 460 } 461 462 463