1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/webui/mediaplayer_ui.h" 6 7 #include "base/command_line.h" 8 #include "base/logging.h" 9 #include "base/memory/singleton.h" 10 #include "base/memory/weak_ptr.h" 11 #include "base/message_loop.h" 12 #include "base/path_service.h" 13 #include "base/string_piece.h" 14 #include "base/string_util.h" 15 #include "base/threading/thread.h" 16 #include "base/time.h" 17 #include "base/values.h" 18 #include "chrome/browser/bookmarks/bookmark_model.h" 19 #include "chrome/browser/download/download_manager.h" 20 #include "chrome/browser/download/download_util.h" 21 #include "chrome/browser/extensions/file_manager_util.h" 22 #include "chrome/browser/history/history_types.h" 23 #include "chrome/browser/metrics/user_metrics.h" 24 #include "chrome/browser/profiles/profile.h" 25 #include "chrome/browser/tabs/tab_strip_model.h" 26 #include "chrome/browser/ui/browser.h" 27 #include "chrome/browser/ui/browser_list.h" 28 #include "chrome/browser/ui/browser_window.h" 29 #include "chrome/browser/ui/webui/favicon_source.h" 30 #include "chrome/common/chrome_paths.h" 31 #include "chrome/common/chrome_switches.h" 32 #include "chrome/common/jstemplate_builder.h" 33 #include "chrome/common/net/url_fetcher.h" 34 #include "chrome/common/time_format.h" 35 #include "chrome/common/url_constants.h" 36 #include "content/browser/browser_thread.h" 37 #include "content/browser/tab_contents/tab_contents.h" 38 #include "grit/browser_resources.h" 39 #include "grit/chromium_strings.h" 40 #include "grit/generated_resources.h" 41 #include "grit/locale_settings.h" 42 #include "net/base/escape.h" 43 #include "net/base/load_flags.h" 44 #include "net/url_request/url_request_job.h" 45 #include "ui/base/resource/resource_bundle.h" 46 47 #if defined(OS_CHROMEOS) 48 #include "chrome/browser/chromeos/frame/panel_browser_view.h" 49 #endif 50 51 static const char kPropertyPath[] = "path"; 52 static const char kPropertyForce[] = "force"; 53 static const char kPropertyOffset[] = "currentOffset"; 54 static const char kPropertyError[] = "error"; 55 56 static const char* kMediaplayerURL = "chrome://mediaplayer"; 57 static const char* kMediaplayerPlaylistURL = "chrome://mediaplayer#playlist"; 58 static const int kPopupLeft = 0; 59 static const int kPopupTop = 0; 60 static const int kPopupWidth = 350; 61 static const int kPopupHeight = 300; 62 63 class MediaplayerUIHTMLSource : public ChromeURLDataManager::DataSource { 64 public: 65 explicit MediaplayerUIHTMLSource(bool is_playlist); 66 67 // Called when the network layer has requested a resource underneath 68 // the path we registered. 69 virtual void StartDataRequest(const std::string& path, 70 bool is_incognito, 71 int request_id); 72 virtual std::string GetMimeType(const std::string&) const { 73 return "text/html"; 74 } 75 76 private: 77 ~MediaplayerUIHTMLSource() {} 78 bool is_playlist_; 79 80 DISALLOW_COPY_AND_ASSIGN(MediaplayerUIHTMLSource); 81 }; 82 83 // The handler for Javascript messages related to the "mediaplayer" view. 84 class MediaplayerHandler : public WebUIMessageHandler, 85 public base::SupportsWeakPtr<MediaplayerHandler> { 86 public: 87 88 struct MediaUrl { 89 MediaUrl() {} 90 explicit MediaUrl(const GURL& newurl) 91 : url(newurl), 92 haderror(false) {} 93 GURL url; 94 bool haderror; 95 }; 96 typedef std::vector<MediaUrl> UrlVector; 97 98 explicit MediaplayerHandler(bool is_playlist); 99 100 virtual ~MediaplayerHandler(); 101 102 // Init work after Attach. 103 void Init(bool is_playlist, TabContents* contents); 104 105 // WebUIMessageHandler implementation. 106 virtual WebUIMessageHandler* Attach(WebUI* web_ui); 107 virtual void RegisterMessages(); 108 109 // Callback for the "currentOffsetChanged" message. 110 void HandleCurrentOffsetChanged(const ListValue* args); 111 112 void FirePlaylistChanged(const std::string& path, 113 bool force, 114 int offset); 115 116 void PlaybackMediaFile(const GURL& url); 117 118 void EnqueueMediaFileUrl(const GURL& url); 119 120 void GetPlaylistValue(ListValue& args); 121 122 // Callback for the "playbackError" message. 123 void HandlePlaybackError(const ListValue* args); 124 125 // Callback for the "getCurrentPlaylist" message. 126 void HandleGetCurrentPlaylist(const ListValue* args); 127 128 void HandleTogglePlaylist(const ListValue* args); 129 void HandleShowPlaylist(const ListValue* args); 130 void HandleSetCurrentPlaylistOffset(const ListValue* args); 131 void HandleToggleFullscreen(const ListValue* args); 132 133 const UrlVector& GetCurrentPlaylist(); 134 135 int GetCurrentPlaylistOffset(); 136 void SetCurrentPlaylistOffset(int offset); 137 // Sets the playlist for playlist views, since the playlist is 138 // maintained by the mediaplayer itself. Offset is the item in the 139 // playlist which is either now playing, or should be played. 140 void SetCurrentPlaylist(const UrlVector& playlist, int offset); 141 142 private: 143 // The current playlist of urls. 144 UrlVector current_playlist_; 145 // The offset into the current_playlist_ of the currently playing item. 146 int current_offset_; 147 // Indicator of if this handler is a playlist or a mediaplayer. 148 bool is_playlist_; 149 DISALLOW_COPY_AND_ASSIGN(MediaplayerHandler); 150 }; 151 152 //////////////////////////////////////////////////////////////////////////////// 153 // 154 // MediaplayerHTMLSource 155 // 156 //////////////////////////////////////////////////////////////////////////////// 157 158 MediaplayerUIHTMLSource::MediaplayerUIHTMLSource(bool is_playlist) 159 : DataSource(chrome::kChromeUIMediaplayerHost, MessageLoop::current()) { 160 is_playlist_ = is_playlist; 161 } 162 163 void MediaplayerUIHTMLSource::StartDataRequest(const std::string& path, 164 bool is_incognito, 165 int request_id) { 166 DictionaryValue localized_strings; 167 // TODO(dhg): Fix the strings that are currently hardcoded so they 168 // use the localized versions. 169 localized_strings.SetString("errorstring", "Error Playing Back"); 170 171 SetFontAndTextDirection(&localized_strings); 172 173 std::string full_html; 174 175 static const base::StringPiece mediaplayer_html( 176 ResourceBundle::GetSharedInstance().GetRawDataResource( 177 IDR_MEDIAPLAYER_HTML)); 178 179 static const base::StringPiece playlist_html( 180 ResourceBundle::GetSharedInstance().GetRawDataResource( 181 IDR_MEDIAPLAYERPLAYLIST_HTML)); 182 183 if (is_playlist_) { 184 full_html = jstemplate_builder::GetI18nTemplateHtml( 185 playlist_html, &localized_strings); 186 } else { 187 full_html = jstemplate_builder::GetI18nTemplateHtml( 188 mediaplayer_html, &localized_strings); 189 } 190 191 scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); 192 html_bytes->data.resize(full_html.size()); 193 std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin()); 194 195 SendResponse(request_id, html_bytes); 196 } 197 198 //////////////////////////////////////////////////////////////////////////////// 199 // 200 // MediaplayerHandler 201 // 202 //////////////////////////////////////////////////////////////////////////////// 203 MediaplayerHandler::MediaplayerHandler(bool is_playlist) 204 : current_offset_(0), 205 is_playlist_(is_playlist) { 206 } 207 208 MediaplayerHandler::~MediaplayerHandler() { 209 } 210 211 WebUIMessageHandler* MediaplayerHandler::Attach(WebUI* web_ui) { 212 // Create our favicon data source. 213 Profile* profile = web_ui->GetProfile(); 214 profile->GetChromeURLDataManager()->AddDataSource( 215 new FaviconSource(profile)); 216 217 return WebUIMessageHandler::Attach(web_ui); 218 } 219 220 void MediaplayerHandler::Init(bool is_playlist, TabContents* contents) { 221 MediaPlayer* player = MediaPlayer::GetInstance(); 222 if (!is_playlist) { 223 player->SetNewHandler(this, contents); 224 } else { 225 player->RegisterNewPlaylistHandler(this, contents); 226 } 227 } 228 229 void MediaplayerHandler::RegisterMessages() { 230 web_ui_->RegisterMessageCallback("currentOffsetChanged", 231 NewCallback(this, &MediaplayerHandler::HandleCurrentOffsetChanged)); 232 web_ui_->RegisterMessageCallback("playbackError", 233 NewCallback(this, &MediaplayerHandler::HandlePlaybackError)); 234 web_ui_->RegisterMessageCallback("getCurrentPlaylist", 235 NewCallback(this, &MediaplayerHandler::HandleGetCurrentPlaylist)); 236 web_ui_->RegisterMessageCallback("togglePlaylist", 237 NewCallback(this, &MediaplayerHandler::HandleTogglePlaylist)); 238 web_ui_->RegisterMessageCallback("setCurrentPlaylistOffset", 239 NewCallback(this, &MediaplayerHandler::HandleSetCurrentPlaylistOffset)); 240 web_ui_->RegisterMessageCallback("toggleFullscreen", 241 NewCallback(this, &MediaplayerHandler::HandleToggleFullscreen)); 242 web_ui_->RegisterMessageCallback("showPlaylist", 243 NewCallback(this, &MediaplayerHandler::HandleShowPlaylist)); 244 } 245 246 void MediaplayerHandler::GetPlaylistValue(ListValue& urls) { 247 for (size_t x = 0; x < current_playlist_.size(); x++) { 248 DictionaryValue* url_value = new DictionaryValue(); 249 url_value->SetString(kPropertyPath, current_playlist_[x].url.spec()); 250 url_value->SetBoolean(kPropertyError, current_playlist_[x].haderror); 251 urls.Append(url_value); 252 } 253 } 254 255 void MediaplayerHandler::PlaybackMediaFile(const GURL& url) { 256 current_playlist_.push_back(MediaplayerHandler::MediaUrl(url)); 257 FirePlaylistChanged(url.spec(), true, current_playlist_.size() - 1); 258 MediaPlayer::GetInstance()->NotifyPlaylistChanged(); 259 } 260 261 const MediaplayerHandler::UrlVector& MediaplayerHandler::GetCurrentPlaylist() { 262 return current_playlist_; 263 } 264 265 int MediaplayerHandler::GetCurrentPlaylistOffset() { 266 return current_offset_; 267 } 268 269 void MediaplayerHandler::HandleToggleFullscreen(const ListValue* args) { 270 MediaPlayer::GetInstance()->ToggleFullscreen(); 271 } 272 273 void MediaplayerHandler::HandleSetCurrentPlaylistOffset(const ListValue* args) { 274 int id; 275 CHECK(ExtractIntegerValue(args, &id)); 276 MediaPlayer::GetInstance()->SetPlaylistOffset(id); 277 } 278 279 void MediaplayerHandler::FirePlaylistChanged(const std::string& path, 280 bool force, 281 int offset) { 282 DictionaryValue info_value; 283 ListValue urls; 284 GetPlaylistValue(urls); 285 info_value.SetString(kPropertyPath, path); 286 info_value.SetBoolean(kPropertyForce, force); 287 info_value.SetInteger(kPropertyOffset, offset); 288 web_ui_->CallJavascriptFunction("playlistChanged", info_value, urls); 289 } 290 291 void MediaplayerHandler::SetCurrentPlaylistOffset(int offset) { 292 current_offset_ = offset; 293 FirePlaylistChanged(std::string(), true, current_offset_); 294 } 295 296 void MediaplayerHandler::SetCurrentPlaylist( 297 const MediaplayerHandler::UrlVector& playlist, int offset) { 298 current_playlist_ = playlist; 299 current_offset_ = offset; 300 FirePlaylistChanged(std::string(), false, current_offset_); 301 } 302 303 void MediaplayerHandler::EnqueueMediaFileUrl(const GURL& url) { 304 current_playlist_.push_back(MediaplayerHandler::MediaUrl(url)); 305 FirePlaylistChanged(url.spec(), false, current_offset_); 306 MediaPlayer::GetInstance()->NotifyPlaylistChanged(); 307 } 308 309 void MediaplayerHandler::HandleCurrentOffsetChanged(const ListValue* args) { 310 CHECK(ExtractIntegerValue(args, ¤t_offset_)); 311 MediaPlayer::GetInstance()->NotifyPlaylistChanged(); 312 } 313 314 void MediaplayerHandler::HandlePlaybackError(const ListValue* args) { 315 std::string error; 316 std::string url; 317 // Get path string. 318 if (args->GetString(0, &error)) 319 LOG(ERROR) << "Playback error" << error; 320 if (args->GetString(1, &url)) { 321 for (size_t x = 0; x < current_playlist_.size(); x++) { 322 if (current_playlist_[x].url == GURL(url)) { 323 current_playlist_[x].haderror = true; 324 } 325 } 326 FirePlaylistChanged(std::string(), false, current_offset_); 327 } 328 } 329 330 void MediaplayerHandler::HandleGetCurrentPlaylist(const ListValue* args) { 331 FirePlaylistChanged(std::string(), false, current_offset_); 332 } 333 334 void MediaplayerHandler::HandleTogglePlaylist(const ListValue* args) { 335 MediaPlayer::GetInstance()->TogglePlaylistWindowVisible(); 336 } 337 338 void MediaplayerHandler::HandleShowPlaylist(const ListValue* args) { 339 MediaPlayer::GetInstance()->ShowPlaylistWindow(); 340 } 341 342 //////////////////////////////////////////////////////////////////////////////// 343 // 344 // Mediaplayer 345 // 346 //////////////////////////////////////////////////////////////////////////////// 347 348 // Allows InvokeLater without adding refcounting. This class is a Singleton and 349 // won't be deleted until it's last InvokeLater is run. 350 DISABLE_RUNNABLE_METHOD_REFCOUNT(MediaPlayer); 351 352 MediaPlayer::~MediaPlayer() { 353 } 354 355 // static 356 MediaPlayer* MediaPlayer::GetInstance() { 357 return Singleton<MediaPlayer>::get(); 358 } 359 360 void MediaPlayer::EnqueueMediaFile(Profile* profile, const FilePath& file_path, 361 Browser* creator) { 362 static GURL origin_url(kMediaplayerURL); 363 GURL url; 364 if (!FileManagerUtil::ConvertFileToFileSystemUrl(profile, file_path, 365 origin_url, &url)) { 366 } 367 EnqueueMediaFileUrl(url, creator); 368 } 369 370 void MediaPlayer::EnqueueMediaFileUrl(const GURL& url, Browser* creator) { 371 if (handler_ == NULL) { 372 unhandled_urls_.push_back(url); 373 PopupMediaPlayer(creator); 374 } else { 375 handler_->EnqueueMediaFileUrl(url); 376 } 377 } 378 379 void MediaPlayer::ForcePlayMediaFile(Profile* profile, 380 const FilePath& file_path, 381 Browser* creator) { 382 static GURL origin_url(kMediaplayerURL); 383 GURL url; 384 if (!FileManagerUtil::ConvertFileToFileSystemUrl(profile, file_path, 385 origin_url, &url)) { 386 } 387 ForcePlayMediaURL(url, creator); 388 } 389 390 void MediaPlayer::ForcePlayMediaURL(const GURL& url, Browser* creator) { 391 if (handler_ == NULL) { 392 unhandled_urls_.push_back(url); 393 PopupMediaPlayer(creator); 394 } else { 395 handler_->PlaybackMediaFile(url); 396 } 397 } 398 399 void MediaPlayer::TogglePlaylistWindowVisible() { 400 if (playlist_browser_) { 401 ClosePlaylistWindow(); 402 } else { 403 ShowPlaylistWindow(); 404 } 405 } 406 407 void MediaPlayer::ShowPlaylistWindow() { 408 if (playlist_browser_ == NULL) { 409 PopupPlaylist(NULL); 410 } 411 } 412 413 void MediaPlayer::ClosePlaylistWindow() { 414 if (playlist_browser_ != NULL) { 415 playlist_browser_->window()->Close(); 416 } 417 } 418 419 void MediaPlayer::SetPlaylistOffset(int offset) { 420 if (handler_) { 421 handler_->SetCurrentPlaylistOffset(offset); 422 } 423 if (playlist_) { 424 playlist_->SetCurrentPlaylistOffset(offset); 425 } 426 } 427 428 void MediaPlayer::SetNewHandler(MediaplayerHandler* handler, 429 TabContents* contents) { 430 handler_ = handler; 431 mediaplayer_tab_ = contents; 432 RegisterListeners(); 433 for (size_t x = 0; x < unhandled_urls_.size(); x++) { 434 handler_->EnqueueMediaFileUrl(unhandled_urls_[x]); 435 } 436 unhandled_urls_.clear(); 437 } 438 439 void MediaPlayer::RegisterListeners() { 440 registrar_.RemoveAll(); 441 if (playlist_tab_) { 442 registrar_.Add(this, 443 NotificationType::TAB_CONTENTS_DESTROYED, 444 Source<TabContents>(playlist_tab_)); 445 } 446 if (mediaplayer_tab_) { 447 registrar_.Add(this, 448 NotificationType::TAB_CONTENTS_DESTROYED, 449 Source<TabContents>(mediaplayer_tab_)); 450 } 451 }; 452 453 void MediaPlayer::Observe(NotificationType type, 454 const NotificationSource& source, 455 const NotificationDetails& details) { 456 DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED); 457 if (Source<TabContents>(source).ptr() == mediaplayer_tab_) { 458 RemoveHandler(handler_); 459 RegisterListeners(); 460 ClosePlaylistWindow(); 461 } else if (Source<TabContents>(source).ptr() == playlist_tab_) { 462 RemovePlaylistHandler(playlist_); 463 RegisterListeners(); 464 } 465 } 466 467 void MediaPlayer::RegisterNewPlaylistHandler(MediaplayerHandler* handler, 468 TabContents* contents) { 469 playlist_ = handler; 470 playlist_tab_ = contents; 471 RegisterListeners(); 472 NotifyPlaylistChanged(); 473 } 474 475 void MediaPlayer::RemovePlaylistHandler(MediaplayerHandler* handler) { 476 if (handler == playlist_) { 477 playlist_ = NULL; 478 playlist_browser_ = NULL; 479 playlist_tab_ = NULL; 480 } 481 } 482 483 void MediaPlayer::NotifyPlaylistChanged() { 484 if (handler_ && playlist_) { 485 playlist_->SetCurrentPlaylist(handler_->GetCurrentPlaylist(), 486 handler_->GetCurrentPlaylistOffset()); 487 } 488 } 489 490 void MediaPlayer::ToggleFullscreen() { 491 if (handler_ && mediaplayer_browser_) { 492 mediaplayer_browser_->ToggleFullscreenMode(); 493 } 494 } 495 496 void MediaPlayer::RemoveHandler(MediaplayerHandler* handler) { 497 if (handler == handler_) { 498 handler_ = NULL; 499 mediaplayer_browser_ = NULL; 500 mediaplayer_tab_ = NULL; 501 } 502 } 503 504 void MediaPlayer::PopupPlaylist(Browser* creator) { 505 Profile* profile = BrowserList::GetLastActive()->profile(); 506 playlist_browser_ = Browser::CreateForType(Browser::TYPE_APP_PANEL, 507 profile); 508 playlist_browser_->AddSelectedTabWithURL(GURL(kMediaplayerPlaylistURL), 509 PageTransition::LINK); 510 playlist_browser_->window()->SetBounds(gfx::Rect(kPopupLeft, 511 kPopupTop, 512 kPopupWidth, 513 kPopupHeight)); 514 playlist_browser_->window()->Show(); 515 } 516 517 void MediaPlayer::PopupMediaPlayer(Browser* creator) { 518 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { 519 BrowserThread::PostTask( 520 BrowserThread::UI, FROM_HERE, 521 NewRunnableMethod(this, &MediaPlayer::PopupMediaPlayer, 522 static_cast<Browser*>(NULL))); 523 return; 524 } 525 Profile* profile = BrowserList::GetLastActive()->profile(); 526 mediaplayer_browser_ = Browser::CreateForType(Browser::TYPE_APP_PANEL, 527 profile); 528 #if defined(OS_CHROMEOS) 529 // Since we are on chromeos, popups should be a PanelBrowserView, 530 // so we can just cast it. 531 if (creator) { 532 chromeos::PanelBrowserView* creatorview = 533 static_cast<chromeos::PanelBrowserView*>(creator->window()); 534 chromeos::PanelBrowserView* view = 535 static_cast<chromeos::PanelBrowserView*>( 536 mediaplayer_browser_->window()); 537 view->SetCreatorView(creatorview); 538 } 539 #endif 540 mediaplayer_browser_->AddSelectedTabWithURL(GURL(kMediaplayerURL), 541 PageTransition::LINK); 542 mediaplayer_browser_->window()->SetBounds(gfx::Rect(kPopupLeft, 543 kPopupTop, 544 kPopupWidth, 545 kPopupHeight)); 546 mediaplayer_browser_->window()->Show(); 547 } 548 549 net::URLRequestJob* MediaPlayer::MaybeIntercept(net::URLRequest* request) { 550 // Don't attempt to intercept here as we want to wait until the mime 551 // type is fully determined. 552 return NULL; 553 } 554 555 // This is the list of mime types currently supported by the Google 556 // Document Viewer. 557 static const char* const supported_mime_type_list[] = { 558 "audio/mpeg", 559 "video/mp4", 560 "audio/mp3" 561 }; 562 563 net::URLRequestJob* MediaPlayer::MaybeInterceptResponse( 564 net::URLRequest* request) { 565 // Do not intercept this request if it is a download. 566 if (request->load_flags() & net::LOAD_IS_DOWNLOAD) { 567 return NULL; 568 } 569 570 std::string mime_type; 571 request->GetMimeType(&mime_type); 572 // If it is in our list of known URLs, enqueue the url then 573 // Cancel the request so the mediaplayer can handle it when 574 // it hits it in the playlist. 575 if (supported_mime_types_.find(mime_type) != supported_mime_types_.end()) { 576 if (request->referrer() != chrome::kChromeUIMediaplayerURL && 577 !request->referrer().empty()) { 578 EnqueueMediaFileUrl(request->url(), NULL); 579 request->Cancel(); 580 } 581 } 582 return NULL; 583 } 584 585 MediaPlayer::MediaPlayer() 586 : handler_(NULL), 587 playlist_(NULL), 588 playlist_browser_(NULL), 589 mediaplayer_browser_(NULL), 590 mediaplayer_tab_(NULL), 591 playlist_tab_(NULL) { 592 for (size_t i = 0; i < arraysize(supported_mime_type_list); ++i) { 593 supported_mime_types_.insert(supported_mime_type_list[i]); 594 } 595 }; 596 597 //////////////////////////////////////////////////////////////////////////////// 598 // 599 // MediaplayerUIContents 600 // 601 //////////////////////////////////////////////////////////////////////////////// 602 603 MediaplayerUI::MediaplayerUI(TabContents* contents) : WebUI(contents) { 604 const GURL& url = contents->GetURL(); 605 bool is_playlist = (url.ref() == "playlist"); 606 MediaplayerHandler* handler = new MediaplayerHandler(is_playlist); 607 AddMessageHandler(handler->Attach(this)); 608 if (is_playlist) { 609 handler->Init(true, contents); 610 } else { 611 handler->Init(false, contents); 612 } 613 614 MediaplayerUIHTMLSource* html_source = 615 new MediaplayerUIHTMLSource(is_playlist); 616 617 // Set up the chrome://mediaplayer/ source. 618 contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source); 619 } 620