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 | B_OVERLAY_FILTER_VERTICAL); 172 if (ret == B_OK) { 173 fOverlayKeyColor = key; 174 SetLowColor(key); 175 snooze(20000); 176 FillRect(fVideoFrame, B_SOLID_LOW); 177 Sync(); 178 // use overlay from here on 179 _SetOverlayMode(true); 180 181 // update restrictions 182 overlay_restrictions restrictions; 183 if (bitmap->GetOverlayRestrictions(&restrictions) == B_OK) 184 fOverlayRestrictions = restrictions; 185 } else { 186 // try again next time 187 // synchronous draw 188 FillRect(fVideoFrame); 189 Sync(); 190 } 191 } else { 192 // transfer overlay channel 193 rgb_color key; 194 SetViewOverlay(bitmap, bitmap->Bounds(), fVideoFrame, 195 &key, B_FOLLOW_ALL, B_OVERLAY_FILTER_HORIZONTAL 196 | B_OVERLAY_FILTER_VERTICAL 197 | B_OVERLAY_TRANSFER_CHANNEL); 198 } 199 } else if (fOverlayMode 200 && (bitmap->Flags() & B_BITMAP_WILL_OVERLAY) == 0) { 201 _SetOverlayMode(false); 202 ClearViewOverlay(); 203 SetViewColor(B_TRANSPARENT_COLOR); 204 } 205 if (!fOverlayMode) { 206 if (fSubtitleChanged) { 207 _LayoutSubtitle(); 208 Invalidate(fVideoFrame | fSubtitleFrame); 209 } else if (fHasSubtitle 210 && fVideoFrame.Intersects(fSubtitleFrame)) { 211 Invalidate(fVideoFrame); 212 } else 213 _DrawBitmap(bitmap); 214 } 215 216 UnlockBitmap(); 217 } 218 UnlockLooper(); 219 } 220 221 222 void 223 VideoView::GetOverlayScaleLimits(float* minScale, float* maxScale) const 224 { 225 *minScale = max_c(fOverlayRestrictions.min_width_scale, 226 fOverlayRestrictions.min_height_scale); 227 *maxScale = max_c(fOverlayRestrictions.max_width_scale, 228 fOverlayRestrictions.max_height_scale); 229 } 230 231 232 void 233 VideoView::OverlayScreenshotPrepare() 234 { 235 // TODO: Do nothing if the current bitmap is in RGB color space 236 // and no overlay. Otherwise, convert current bitmap to RGB color 237 // space an draw it in place of the normal display. 238 } 239 240 241 void 242 VideoView::OverlayScreenshotCleanup() 243 { 244 // TODO: Do nothing if the current bitmap is in RGB color space 245 // and no overlay. Otherwise clean view area with overlay color. 246 } 247 248 249 bool 250 VideoView::UseOverlays() const 251 { 252 return fUseOverlays; 253 } 254 255 256 bool 257 VideoView::IsOverlayActive() 258 { 259 bool active = false; 260 if (LockBitmap()) { 261 active = fOverlayMode; 262 UnlockBitmap(); 263 } 264 return active; 265 } 266 267 268 void 269 VideoView::DisableOverlay() 270 { 271 if (!fOverlayMode) 272 return; 273 274 FillRect(Bounds()); 275 Sync(); 276 277 ClearViewOverlay(); 278 snooze(20000); 279 Sync(); 280 _SetOverlayMode(false); 281 } 282 283 284 void 285 VideoView::SetPlaying(bool playing) 286 { 287 fIsPlaying = playing; 288 } 289 290 291 void 292 VideoView::SetFullscreen(bool fullScreen) 293 { 294 fIsFullscreen = fullScreen; 295 } 296 297 298 void 299 VideoView::SetVideoFrame(const BRect& frame) 300 { 301 if (fVideoFrame == frame) 302 return; 303 304 BRegion invalid(fVideoFrame | frame); 305 invalid.Exclude(frame); 306 Invalidate(&invalid); 307 308 fVideoFrame = frame; 309 310 fSubtitleBitmap->SetVideoBounds(fVideoFrame.OffsetToCopy(B_ORIGIN)); 311 _LayoutSubtitle(); 312 } 313 314 315 void 316 VideoView::SetSubTitle(const char* text) 317 { 318 BRect oldSubtitleFrame = fSubtitleFrame; 319 320 if (text == NULL || text[0] == '\0') { 321 fHasSubtitle = false; 322 fSubtitleChanged = true; 323 } else { 324 fHasSubtitle = true; 325 // If the subtitle frame still needs to be invalidated during 326 // normal playback, make sure we don't unset the fSubtitleChanged 327 // flag. It will be reset after drawing the subtitle once. 328 fSubtitleChanged = fSubtitleBitmap->SetText(text) || fSubtitleChanged; 329 if (fSubtitleChanged) 330 _LayoutSubtitle(); 331 } 332 333 if (!fIsPlaying && Window() != NULL) { 334 // If we are playing, the new subtitle will be displayed, 335 // or the old one removed from screen, as soon as the next 336 // frame is shown. Otherwise we need to invalidate manually. 337 // But we are not in the window thread and we shall not lock 338 // it or we may dead-locks. 339 BMessage message(MSG_INVALIDATE); 340 message.AddRect("dirty", oldSubtitleFrame | fSubtitleFrame); 341 Window()->PostMessage(&message); 342 } 343 } 344 345 346 void 347 VideoView::SetSubTitleMaxBottom(float bottom) 348 { 349 if (bottom == fSubtitleMaxButtom) 350 return; 351 352 fSubtitleMaxButtom = bottom; 353 354 BRect oldSubtitleFrame = fSubtitleFrame; 355 _LayoutSubtitle(); 356 Invalidate(fSubtitleFrame | oldSubtitleFrame); 357 } 358 359 360 // #pragma mark - 361 362 363 void 364 VideoView::_DrawBitmap(const BBitmap* bitmap) 365 { 366 SetDrawingMode(B_OP_COPY); 367 uint32 options = B_WAIT_FOR_RETRACE; 368 if (fUseBilinearScaling) 369 options |= B_FILTER_BITMAP_BILINEAR; 370 371 DrawBitmapAsync(bitmap, bitmap->Bounds(), fVideoFrame, options); 372 } 373 374 375 void 376 VideoView::_DrawSubtitle() 377 { 378 SetDrawingMode(B_OP_ALPHA); 379 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 380 381 DrawBitmapAsync(fSubtitleBitmap->Bitmap(), fSubtitleFrame.LeftTop()); 382 383 // Unless the subtitle frame intersects the video frame, we don't have 384 // to draw the subtitle again. 385 fSubtitleChanged = false; 386 } 387 388 389 void 390 VideoView::_AdoptGlobalSettings() 391 { 392 mpSettings settings; 393 Settings::Default()->Get(settings); 394 395 fUseOverlays = settings.useOverlays; 396 fUseBilinearScaling = settings.scaleBilinear; 397 398 switch (settings.subtitleSize) { 399 case mpSettings::SUBTITLE_SIZE_SMALL: 400 fSubtitleBitmap->SetCharsPerLine(45.0); 401 break; 402 case mpSettings::SUBTITLE_SIZE_MEDIUM: 403 fSubtitleBitmap->SetCharsPerLine(36.0); 404 break; 405 case mpSettings::SUBTITLE_SIZE_LARGE: 406 fSubtitleBitmap->SetCharsPerLine(32.0); 407 break; 408 } 409 410 fSubtitlePlacement = settings.subtitlePlacement; 411 412 _LayoutSubtitle(); 413 Invalidate(); 414 } 415 416 417 void 418 VideoView::_SetOverlayMode(bool overlayMode) 419 { 420 fOverlayMode = overlayMode; 421 fSubtitleBitmap->SetOverlayMode(overlayMode); 422 } 423 424 425 void 426 VideoView::_LayoutSubtitle() 427 { 428 if (!fHasSubtitle) 429 return; 430 431 const BBitmap* subtitleBitmap = fSubtitleBitmap->Bitmap(); 432 if (subtitleBitmap == NULL) 433 return; 434 435 fSubtitleFrame = subtitleBitmap->Bounds(); 436 437 BPoint offset; 438 offset.x = (fVideoFrame.left + fVideoFrame.right 439 - fSubtitleFrame.Width()) / 2; 440 switch (fSubtitlePlacement) { 441 default: 442 case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_VIDEO: 443 offset.y = min_c(fSubtitleMaxButtom, fVideoFrame.bottom) 444 - fSubtitleFrame.Height(); 445 break; 446 case mpSettings::SUBTITLE_PLACEMENT_BOTTOM_OF_SCREEN: 447 { 448 // Center between video and screen bottom, if there is still 449 // enough room. 450 float centeredOffset = (fVideoFrame.bottom + fSubtitleMaxButtom 451 - fSubtitleFrame.Height()) / 2; 452 float maxOffset = fSubtitleMaxButtom - fSubtitleFrame.Height(); 453 offset.y = min_c(centeredOffset, maxOffset); 454 break; 455 } 456 } 457 458 fSubtitleFrame.OffsetTo(offset); 459 } 460 461 462