// 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, ¤t_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);
}