普通文本  |  620行  |  20.37 KB

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/webui/mediaplayer_ui.h"

#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/string_piece.h"
#include "base/string_util.h"
#include "base/threading/thread.h"
#include "base/time.h"
#include "base/values.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/download/download_manager.h"
#include "chrome/browser/download/download_util.h"
#include "chrome/browser/extensions/file_manager_util.h"
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/webui/favicon_source.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/jstemplate_builder.h"
#include "chrome/common/net/url_fetcher.h"
#include "chrome/common/time_format.h"
#include "chrome/common/url_constants.h"
#include "content/browser/browser_thread.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "grit/browser_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "net/base/escape.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request_job.h"
#include "ui/base/resource/resource_bundle.h"

#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/frame/panel_browser_view.h"
#endif

static const char kPropertyPath[] = "path";
static const char kPropertyForce[] = "force";
static const char kPropertyOffset[] = "currentOffset";
static const char kPropertyError[] = "error";

static const char* kMediaplayerURL = "chrome://mediaplayer";
static const char* kMediaplayerPlaylistURL = "chrome://mediaplayer#playlist";
static const int kPopupLeft = 0;
static const int kPopupTop = 0;
static const int kPopupWidth = 350;
static const int kPopupHeight = 300;

class MediaplayerUIHTMLSource : public ChromeURLDataManager::DataSource {
 public:
  explicit MediaplayerUIHTMLSource(bool is_playlist);

  // Called when the network layer has requested a resource underneath
  // the path we registered.
  virtual void StartDataRequest(const std::string& path,
                                bool is_incognito,
                                int request_id);
  virtual std::string GetMimeType(const std::string&) const {
    return "text/html";
  }

 private:
  ~MediaplayerUIHTMLSource() {}
  bool is_playlist_;

  DISALLOW_COPY_AND_ASSIGN(MediaplayerUIHTMLSource);
};

// The handler for Javascript messages related to the "mediaplayer" view.
class MediaplayerHandler : public WebUIMessageHandler,
                           public base::SupportsWeakPtr<MediaplayerHandler> {
 public:

  struct MediaUrl {
    MediaUrl() {}
    explicit MediaUrl(const GURL& newurl)
        : url(newurl),
          haderror(false) {}
    GURL url;
    bool haderror;
  };
  typedef std::vector<MediaUrl> UrlVector;

  explicit MediaplayerHandler(bool is_playlist);

  virtual ~MediaplayerHandler();

  // Init work after Attach.
  void Init(bool is_playlist, TabContents* contents);

  // WebUIMessageHandler implementation.
  virtual WebUIMessageHandler* Attach(WebUI* web_ui);
  virtual void RegisterMessages();

  // Callback for the "currentOffsetChanged" message.
  void HandleCurrentOffsetChanged(const ListValue* args);

  void FirePlaylistChanged(const std::string& path,
                           bool force,
                           int offset);

  void PlaybackMediaFile(const GURL& url);

  void EnqueueMediaFileUrl(const GURL& url);

  void GetPlaylistValue(ListValue& args);

  // Callback for the "playbackError" message.
  void HandlePlaybackError(const ListValue* args);

  // Callback for the "getCurrentPlaylist" message.
  void HandleGetCurrentPlaylist(const ListValue* args);

  void HandleTogglePlaylist(const ListValue* args);
  void HandleShowPlaylist(const ListValue* args);
  void HandleSetCurrentPlaylistOffset(const ListValue* args);
  void HandleToggleFullscreen(const ListValue* args);

  const UrlVector& GetCurrentPlaylist();

  int GetCurrentPlaylistOffset();
  void SetCurrentPlaylistOffset(int offset);
  // Sets  the playlist for playlist views, since the playlist is
  // maintained by the mediaplayer itself.  Offset is the item in the
  // playlist which is either now playing, or should be played.
  void SetCurrentPlaylist(const UrlVector& playlist, int offset);

 private:
  // The current playlist of urls.
  UrlVector current_playlist_;
  // The offset into the current_playlist_ of the currently playing item.
  int current_offset_;
  // Indicator of if this handler is a playlist or a mediaplayer.
  bool is_playlist_;
  DISALLOW_COPY_AND_ASSIGN(MediaplayerHandler);
};

////////////////////////////////////////////////////////////////////////////////
//
// MediaplayerHTMLSource
//
////////////////////////////////////////////////////////////////////////////////

MediaplayerUIHTMLSource::MediaplayerUIHTMLSource(bool is_playlist)
    : DataSource(chrome::kChromeUIMediaplayerHost, MessageLoop::current()) {
  is_playlist_ = is_playlist;
}

void MediaplayerUIHTMLSource::StartDataRequest(const std::string& path,
                                               bool is_incognito,
                                               int request_id) {
  DictionaryValue localized_strings;
  // TODO(dhg): Fix the strings that are currently hardcoded so they
  // use the localized versions.
  localized_strings.SetString("errorstring", "Error Playing Back");

  SetFontAndTextDirection(&localized_strings);

  std::string full_html;

  static const base::StringPiece mediaplayer_html(
      ResourceBundle::GetSharedInstance().GetRawDataResource(
          IDR_MEDIAPLAYER_HTML));

  static const base::StringPiece playlist_html(
      ResourceBundle::GetSharedInstance().GetRawDataResource(
          IDR_MEDIAPLAYERPLAYLIST_HTML));

  if (is_playlist_) {
    full_html = jstemplate_builder::GetI18nTemplateHtml(
        playlist_html, &localized_strings);
  } else {
    full_html = jstemplate_builder::GetI18nTemplateHtml(
        mediaplayer_html, &localized_strings);
  }

  scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes);
  html_bytes->data.resize(full_html.size());
  std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin());

  SendResponse(request_id, html_bytes);
}

////////////////////////////////////////////////////////////////////////////////
//
// MediaplayerHandler
//
////////////////////////////////////////////////////////////////////////////////
MediaplayerHandler::MediaplayerHandler(bool is_playlist)
    : current_offset_(0),
      is_playlist_(is_playlist) {
}

MediaplayerHandler::~MediaplayerHandler() {
}

WebUIMessageHandler* MediaplayerHandler::Attach(WebUI* web_ui) {
  // Create our favicon data source.
  Profile* profile = web_ui->GetProfile();
  profile->GetChromeURLDataManager()->AddDataSource(
      new FaviconSource(profile));

  return WebUIMessageHandler::Attach(web_ui);
}

void MediaplayerHandler::Init(bool is_playlist, TabContents* contents) {
  MediaPlayer* player = MediaPlayer::GetInstance();
  if (!is_playlist) {
    player->SetNewHandler(this, contents);
  } else {
    player->RegisterNewPlaylistHandler(this, contents);
  }
}

void MediaplayerHandler::RegisterMessages() {
  web_ui_->RegisterMessageCallback("currentOffsetChanged",
      NewCallback(this, &MediaplayerHandler::HandleCurrentOffsetChanged));
  web_ui_->RegisterMessageCallback("playbackError",
      NewCallback(this, &MediaplayerHandler::HandlePlaybackError));
  web_ui_->RegisterMessageCallback("getCurrentPlaylist",
      NewCallback(this, &MediaplayerHandler::HandleGetCurrentPlaylist));
  web_ui_->RegisterMessageCallback("togglePlaylist",
      NewCallback(this, &MediaplayerHandler::HandleTogglePlaylist));
  web_ui_->RegisterMessageCallback("setCurrentPlaylistOffset",
      NewCallback(this, &MediaplayerHandler::HandleSetCurrentPlaylistOffset));
  web_ui_->RegisterMessageCallback("toggleFullscreen",
      NewCallback(this, &MediaplayerHandler::HandleToggleFullscreen));
  web_ui_->RegisterMessageCallback("showPlaylist",
      NewCallback(this, &MediaplayerHandler::HandleShowPlaylist));
}

void MediaplayerHandler::GetPlaylistValue(ListValue& urls) {
  for (size_t x = 0; x < current_playlist_.size(); x++) {
    DictionaryValue* url_value = new DictionaryValue();
    url_value->SetString(kPropertyPath, current_playlist_[x].url.spec());
    url_value->SetBoolean(kPropertyError, current_playlist_[x].haderror);
    urls.Append(url_value);
  }
}

void MediaplayerHandler::PlaybackMediaFile(const GURL& url) {
  current_playlist_.push_back(MediaplayerHandler::MediaUrl(url));
  FirePlaylistChanged(url.spec(), true, current_playlist_.size() - 1);
  MediaPlayer::GetInstance()->NotifyPlaylistChanged();
}

const MediaplayerHandler::UrlVector& MediaplayerHandler::GetCurrentPlaylist() {
  return current_playlist_;
}

int MediaplayerHandler::GetCurrentPlaylistOffset() {
  return current_offset_;
}

void MediaplayerHandler::HandleToggleFullscreen(const ListValue* args) {
  MediaPlayer::GetInstance()->ToggleFullscreen();
}

void MediaplayerHandler::HandleSetCurrentPlaylistOffset(const ListValue* args) {
  int id;
  CHECK(ExtractIntegerValue(args, &id));
  MediaPlayer::GetInstance()->SetPlaylistOffset(id);
}

void MediaplayerHandler::FirePlaylistChanged(const std::string& path,
                                             bool force,
                                             int offset) {
  DictionaryValue info_value;
  ListValue urls;
  GetPlaylistValue(urls);
  info_value.SetString(kPropertyPath, path);
  info_value.SetBoolean(kPropertyForce, force);
  info_value.SetInteger(kPropertyOffset, offset);
  web_ui_->CallJavascriptFunction("playlistChanged", info_value, urls);
}

void MediaplayerHandler::SetCurrentPlaylistOffset(int offset) {
  current_offset_ = offset;
  FirePlaylistChanged(std::string(), true, current_offset_);
}

void MediaplayerHandler::SetCurrentPlaylist(
    const MediaplayerHandler::UrlVector& playlist, int offset) {
  current_playlist_ = playlist;
  current_offset_ = offset;
  FirePlaylistChanged(std::string(), false, current_offset_);
}

void MediaplayerHandler::EnqueueMediaFileUrl(const GURL& url) {
  current_playlist_.push_back(MediaplayerHandler::MediaUrl(url));
  FirePlaylistChanged(url.spec(), false, current_offset_);
  MediaPlayer::GetInstance()->NotifyPlaylistChanged();
}

void MediaplayerHandler::HandleCurrentOffsetChanged(const ListValue* args) {
  CHECK(ExtractIntegerValue(args, &current_offset_));
  MediaPlayer::GetInstance()->NotifyPlaylistChanged();
}

void MediaplayerHandler::HandlePlaybackError(const ListValue* args) {
  std::string error;
  std::string url;
  // Get path string.
  if (args->GetString(0, &error))
    LOG(ERROR) << "Playback error" << error;
  if (args->GetString(1, &url)) {
    for (size_t x = 0; x < current_playlist_.size(); x++) {
      if (current_playlist_[x].url == GURL(url)) {
        current_playlist_[x].haderror = true;
      }
    }
    FirePlaylistChanged(std::string(), false, current_offset_);
  }
}

void MediaplayerHandler::HandleGetCurrentPlaylist(const ListValue* args) {
  FirePlaylistChanged(std::string(), false, current_offset_);
}

void MediaplayerHandler::HandleTogglePlaylist(const ListValue* args) {
  MediaPlayer::GetInstance()->TogglePlaylistWindowVisible();
}

void MediaplayerHandler::HandleShowPlaylist(const ListValue* args) {
  MediaPlayer::GetInstance()->ShowPlaylistWindow();
}

////////////////////////////////////////////////////////////////////////////////
//
// Mediaplayer
//
////////////////////////////////////////////////////////////////////////////////

// Allows InvokeLater without adding refcounting. This class is a Singleton and
// won't be deleted until it's last InvokeLater is run.
DISABLE_RUNNABLE_METHOD_REFCOUNT(MediaPlayer);

MediaPlayer::~MediaPlayer() {
}

// static
MediaPlayer* MediaPlayer::GetInstance() {
  return Singleton<MediaPlayer>::get();
}

void MediaPlayer::EnqueueMediaFile(Profile* profile, const FilePath& file_path,
                                   Browser* creator) {
  static GURL origin_url(kMediaplayerURL);
  GURL url;
  if (!FileManagerUtil::ConvertFileToFileSystemUrl(profile, file_path,
                                                   origin_url, &url)) {
  }
  EnqueueMediaFileUrl(url, creator);
}

void MediaPlayer::EnqueueMediaFileUrl(const GURL& url, Browser* creator) {
  if (handler_ == NULL) {
    unhandled_urls_.push_back(url);
    PopupMediaPlayer(creator);
  } else {
    handler_->EnqueueMediaFileUrl(url);
  }
}

void MediaPlayer::ForcePlayMediaFile(Profile* profile,
                                     const FilePath& file_path,
                                     Browser* creator) {
  static GURL origin_url(kMediaplayerURL);
  GURL url;
  if (!FileManagerUtil::ConvertFileToFileSystemUrl(profile, file_path,
                                                   origin_url, &url)) {
  }
  ForcePlayMediaURL(url, creator);
}

void MediaPlayer::ForcePlayMediaURL(const GURL& url, Browser* creator) {
  if (handler_ == NULL) {
    unhandled_urls_.push_back(url);
    PopupMediaPlayer(creator);
  } else {
    handler_->PlaybackMediaFile(url);
  }
}

void MediaPlayer::TogglePlaylistWindowVisible() {
  if (playlist_browser_) {
    ClosePlaylistWindow();
  } else {
    ShowPlaylistWindow();
  }
}

void MediaPlayer::ShowPlaylistWindow() {
  if (playlist_browser_ == NULL) {
    PopupPlaylist(NULL);
  }
}

void MediaPlayer::ClosePlaylistWindow() {
  if (playlist_browser_ != NULL) {
    playlist_browser_->window()->Close();
  }
}

void MediaPlayer::SetPlaylistOffset(int offset) {
  if (handler_) {
    handler_->SetCurrentPlaylistOffset(offset);
  }
  if (playlist_) {
    playlist_->SetCurrentPlaylistOffset(offset);
  }
}

void MediaPlayer::SetNewHandler(MediaplayerHandler* handler,
                                TabContents* contents) {
  handler_ = handler;
  mediaplayer_tab_ = contents;
  RegisterListeners();
  for (size_t x = 0; x < unhandled_urls_.size(); x++) {
    handler_->EnqueueMediaFileUrl(unhandled_urls_[x]);
  }
  unhandled_urls_.clear();
}

void MediaPlayer::RegisterListeners() {
  registrar_.RemoveAll();
  if (playlist_tab_) {
    registrar_.Add(this,
                   NotificationType::TAB_CONTENTS_DESTROYED,
                   Source<TabContents>(playlist_tab_));
  }
  if (mediaplayer_tab_) {
    registrar_.Add(this,
                   NotificationType::TAB_CONTENTS_DESTROYED,
                   Source<TabContents>(mediaplayer_tab_));
  }
};

void MediaPlayer::Observe(NotificationType type,
                          const NotificationSource& source,
                          const NotificationDetails& details) {
  DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED);
  if (Source<TabContents>(source).ptr() == mediaplayer_tab_) {
    RemoveHandler(handler_);
    RegisterListeners();
    ClosePlaylistWindow();
  } else if (Source<TabContents>(source).ptr() == playlist_tab_) {
    RemovePlaylistHandler(playlist_);
    RegisterListeners();
  }
}

void MediaPlayer::RegisterNewPlaylistHandler(MediaplayerHandler* handler,
                                             TabContents* contents) {
  playlist_ = handler;
  playlist_tab_ = contents;
  RegisterListeners();
  NotifyPlaylistChanged();
}

void MediaPlayer::RemovePlaylistHandler(MediaplayerHandler* handler) {
  if (handler == playlist_) {
    playlist_ = NULL;
    playlist_browser_ = NULL;
    playlist_tab_ = NULL;
  }
}

void MediaPlayer::NotifyPlaylistChanged() {
  if (handler_ && playlist_) {
    playlist_->SetCurrentPlaylist(handler_->GetCurrentPlaylist(),
                                  handler_->GetCurrentPlaylistOffset());
  }
}

void MediaPlayer::ToggleFullscreen() {
  if (handler_ && mediaplayer_browser_) {
    mediaplayer_browser_->ToggleFullscreenMode();
  }
}

void MediaPlayer::RemoveHandler(MediaplayerHandler* handler) {
  if (handler == handler_) {
    handler_ = NULL;
    mediaplayer_browser_ = NULL;
    mediaplayer_tab_ = NULL;
  }
}

void MediaPlayer::PopupPlaylist(Browser* creator) {
  Profile* profile = BrowserList::GetLastActive()->profile();
  playlist_browser_ = Browser::CreateForType(Browser::TYPE_APP_PANEL,
                                             profile);
  playlist_browser_->AddSelectedTabWithURL(GURL(kMediaplayerPlaylistURL),
                                           PageTransition::LINK);
  playlist_browser_->window()->SetBounds(gfx::Rect(kPopupLeft,
                                                   kPopupTop,
                                                   kPopupWidth,
                                                   kPopupHeight));
  playlist_browser_->window()->Show();
}

void MediaPlayer::PopupMediaPlayer(Browser* creator) {
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableMethod(this, &MediaPlayer::PopupMediaPlayer,
                          static_cast<Browser*>(NULL)));
    return;
  }
  Profile* profile = BrowserList::GetLastActive()->profile();
  mediaplayer_browser_ = Browser::CreateForType(Browser::TYPE_APP_PANEL,
                                                profile);
#if defined(OS_CHROMEOS)
  // Since we are on chromeos, popups should be a PanelBrowserView,
  // so we can just cast it.
  if (creator) {
    chromeos::PanelBrowserView* creatorview =
        static_cast<chromeos::PanelBrowserView*>(creator->window());
    chromeos::PanelBrowserView* view =
        static_cast<chromeos::PanelBrowserView*>(
            mediaplayer_browser_->window());
    view->SetCreatorView(creatorview);
  }
#endif
  mediaplayer_browser_->AddSelectedTabWithURL(GURL(kMediaplayerURL),
                                              PageTransition::LINK);
  mediaplayer_browser_->window()->SetBounds(gfx::Rect(kPopupLeft,
                                                      kPopupTop,
                                                      kPopupWidth,
                                                      kPopupHeight));
  mediaplayer_browser_->window()->Show();
}

net::URLRequestJob* MediaPlayer::MaybeIntercept(net::URLRequest* request) {
  // Don't attempt to intercept here as we want to wait until the mime
  // type is fully determined.
  return NULL;
}

// This is the list of mime types currently supported by the Google
// Document Viewer.
static const char* const supported_mime_type_list[] = {
  "audio/mpeg",
  "video/mp4",
  "audio/mp3"
};

net::URLRequestJob* MediaPlayer::MaybeInterceptResponse(
    net::URLRequest* request) {
  // Do not intercept this request if it is a download.
  if (request->load_flags() & net::LOAD_IS_DOWNLOAD) {
    return NULL;
  }

  std::string mime_type;
  request->GetMimeType(&mime_type);
  // If it is in our list of known URLs, enqueue the url then
  // Cancel the request so the mediaplayer can handle it when
  // it hits it in the playlist.
  if (supported_mime_types_.find(mime_type) != supported_mime_types_.end()) {
    if (request->referrer() != chrome::kChromeUIMediaplayerURL &&
        !request->referrer().empty()) {
      EnqueueMediaFileUrl(request->url(), NULL);
      request->Cancel();
    }
  }
  return NULL;
}

MediaPlayer::MediaPlayer()
    : handler_(NULL),
      playlist_(NULL),
      playlist_browser_(NULL),
      mediaplayer_browser_(NULL),
      mediaplayer_tab_(NULL),
      playlist_tab_(NULL) {
  for (size_t i = 0; i < arraysize(supported_mime_type_list); ++i) {
    supported_mime_types_.insert(supported_mime_type_list[i]);
  }
};

////////////////////////////////////////////////////////////////////////////////
//
// MediaplayerUIContents
//
////////////////////////////////////////////////////////////////////////////////

MediaplayerUI::MediaplayerUI(TabContents* contents) : WebUI(contents) {
  const GURL& url = contents->GetURL();
  bool is_playlist = (url.ref() == "playlist");
  MediaplayerHandler* handler = new MediaplayerHandler(is_playlist);
  AddMessageHandler(handler->Attach(this));
  if (is_playlist) {
    handler->Init(true, contents);
  } else {
    handler->Init(false, contents);
  }

  MediaplayerUIHTMLSource* html_source =
      new MediaplayerUIHTMLSource(is_playlist);

  // Set up the chrome://mediaplayer/ source.
  contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source);
}