| // Copyright 2015 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 <deque> | 
 | #include <iomanip> | 
 | #include <iostream> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "examples/media_test/keystroke.h" | 
 | #include "examples/media_test/media_test.h" | 
 | #include "mojo/application/application_runner_chromium.h" | 
 | #include "mojo/public/c/system/main.h" | 
 | #include "mojo/public/cpp/application/application_connection.h" | 
 | #include "mojo/public/cpp/application/application_delegate.h" | 
 | #include "mojo/public/cpp/application/application_impl.h" | 
 |  | 
 | namespace mojo { | 
 | namespace media { | 
 | namespace examples { | 
 |  | 
 | class MediaTestApp : public mojo::ApplicationDelegate { | 
 |  public: | 
 |   MediaTestApp() {} | 
 |  | 
 |   ~MediaTestApp() override {} | 
 |  | 
 |   // ApplicationDelegate implementation. | 
 |   void Initialize(mojo::ApplicationImpl* app) override { | 
 |     app_ = app; | 
 |     ProcessArgs(app->args()); | 
 |  | 
 |     std::cout << std::endl << "MEDIA TEST" << std::endl << std::endl; | 
 |  | 
 |     if (input_file_names_.empty()) { | 
 |       std::cout << "Please provide the names of the files you want to play;" | 
 |                 << " for example:" << std::endl | 
 |                 << "mojo/devtools/common/mojo_run \\" << std::endl | 
 |                 << "    \"https://core.mojoapps.io/media_test.mojo \\" | 
 |                 << std::endl | 
 |                 << "    http:/localhost/superstition.ogg \\" << std::endl | 
 |                 << "    http:/localhost/higherground.ogg\"" << std::endl | 
 |                 << std::endl; | 
 |       base::MessageLoop::current()->Quit(); | 
 |       return; | 
 |     } | 
 |  | 
 |     std::cout << "    <enter>         play/pause" << std::endl | 
 |               << "    n<enter>        next file" << std::endl | 
 |               << "    p<enter>        previous file" << std::endl | 
 |               << "    <digit><enter>  seek (0% - 90%)" << std::endl | 
 |               << "    q<enter>        quit" << std::endl | 
 |               << std::endl; | 
 |  | 
 |     if (paint_) { | 
 |       std::cout << "    duration   <none>" << std::endl | 
 |                 << "    title      <none>" << std::endl | 
 |                 << "    artist     <none>" << std::endl | 
 |                 << "    album      <none>" << std::endl | 
 |                 << "    publisher  <none>" << std::endl | 
 |                 << "    genre      <none>" << std::endl | 
 |                 << "    composer   <none>" << std::endl | 
 |                 << std::endl | 
 |                 << std::endl | 
 |                 << std::endl | 
 |                 << kUp << std::flush; | 
 |     } | 
 |  | 
 |     CreateNewMediaTest(); | 
 |     Poll(); | 
 |   } | 
 |  | 
 |   bool ConfigureIncomingConnection( | 
 |       mojo::ApplicationConnection* connection) override { | 
 |     return true; | 
 |   } | 
 |  | 
 |  private: | 
 |   static const char* kHome; | 
 |   static const char* kClearLine; | 
 |   static const char* kUp; | 
 |   static constexpr double ns_per_second = 1000000000.0; | 
 |  | 
 |   // Processes arguments. | 
 |   void ProcessArgs(const std::vector<std::string>& args) { | 
 |     for (size_t i = 1; i < args.size(); ++i) { | 
 |       const std::string& arg = args[i]; | 
 |       if (arg == "--paint") { | 
 |         paint_ = true; | 
 |       } else if (arg == "--no-paint") { | 
 |         paint_ = false; | 
 |       } else { | 
 |         input_file_names_.push_back(arg); | 
 |       } | 
 |     } | 
 |  | 
 |     input_file_names_iter_ = input_file_names_.begin(); | 
 |   } | 
 |  | 
 |   // Creates a new MediaTest object to play the file referenced by | 
 |   // input_file_names_iter_. | 
 |   void CreateNewMediaTest() { | 
 |     MOJO_DCHECK(input_file_names_iter_ != input_file_names_.end()); | 
 |     media_test_ = MediaTest::Create(app_, *input_file_names_iter_); | 
 |  | 
 |     metadata_shown_ = false; | 
 |     media_test_->RegisterUpdateCallback( | 
 |         [this]() { HandleMediaTestUpdateCallback(); }); | 
 |  | 
 |     media_test_->Play(); | 
 |   } | 
 |  | 
 |   void HandleMediaTestUpdateCallback() { | 
 |     if (media_test_->state() == MediaState::ENDED && | 
 |         media_test_->previous_state() != MediaState::ENDED) { | 
 |       // MediaTest doesn't appreciate being deleted in this callback. | 
 |       // Next time Poll runs, we move on to the next file. | 
 |       base::MessageLoop::current()->PostTask( | 
 |           FROM_HERE, | 
 |           base::Bind(&MediaTestApp::PlayNext, base::Unretained(this))); | 
 |     } | 
 |  | 
 |     const MediaMetadataPtr& metadata = media_test_->metadata(); | 
 |  | 
 |     if (metadata) { | 
 |       duration_ns_ = metadata->duration; | 
 |     } | 
 |  | 
 |     if (paint_) { | 
 |       // Move the cursor up the terminal so we paint over the old metadata | 
 |       // (7 lines) a blank line and the state line (total of 9 lines). | 
 |       std::cout << kHome << kUp << kUp << kUp << kUp << kUp << kUp << kUp << kUp | 
 |                 << kUp; | 
 |     } | 
 |  | 
 |     if (!paint_ && metadata_shown_) { | 
 |       // Do nothing. | 
 |     } else if (metadata) { | 
 |       metadata_shown_ = true; | 
 |       std::cout << "    duration   " << std::fixed << std::setprecision(1) | 
 |                 << double(metadata->duration) / ns_per_second << " seconds" | 
 |                 << clear_line() << std::endl | 
 |                 << "    title      " | 
 |                 << (metadata->title ? metadata->title : "<none>") | 
 |                 << clear_line() << std::endl | 
 |                 << "    artist     " | 
 |                 << (metadata->artist ? metadata->artist : "<none>") | 
 |                 << clear_line() << std::endl | 
 |                 << "    album      " | 
 |                 << (metadata->album ? metadata->album : "<none>") | 
 |                 << clear_line() << std::endl | 
 |                 << "    publisher  " | 
 |                 << (metadata->publisher ? metadata->publisher : "<none>") | 
 |                 << clear_line() << std::endl | 
 |                 << "    genre      " | 
 |                 << (metadata->genre ? metadata->genre : "<none>") | 
 |                 << clear_line() << std::endl | 
 |                 << "    composer   " | 
 |                 << (metadata->composer ? metadata->composer : "<none>") | 
 |                 << clear_line() << std::endl | 
 |                 << std::endl; | 
 |     } else if (paint_) { | 
 |       std::cout << "    duration   <none>" << kClearLine << std::endl | 
 |                 << "    title      <none>" << kClearLine << std::endl | 
 |                 << "    artist     <none>" << kClearLine << std::endl | 
 |                 << "    album      <none>" << kClearLine << std::endl | 
 |                 << "    publisher  <none>" << kClearLine << std::endl | 
 |                 << "    genre      <none>" << kClearLine << std::endl | 
 |                 << "    composer   <none>" << kClearLine << std::endl | 
 |                 << std::endl; | 
 |     } | 
 |     std::cout << "    " << state_string() << clear_line() << std::endl; | 
 |   } | 
 |  | 
 |   // Returns a string describing the MediaTest object's state. | 
 |   const char* state_string() const { | 
 |     switch (media_test_->state()) { | 
 |       case MediaState::FAULT: | 
 |         return "FAULT"; | 
 |       case MediaState::UNPREPARED: | 
 |         return "unprepared"; | 
 |       case MediaState::PAUSED: | 
 |         return "paused"; | 
 |       case MediaState::PLAYING: | 
 |         return "playing"; | 
 |       case MediaState::ENDED: | 
 |         return "ended"; | 
 |     } | 
 |     return "UNSUPPORTED STATE VALUE"; | 
 |   } | 
 |  | 
 |   // Handles a keystroke. | 
 |   void HandleKeystroke(char keystroke) { | 
 |     switch (keystroke) { | 
 |       case '\n': | 
 |         TogglePlayPause(); | 
 |         break; | 
 |       case 'q': | 
 |         base::MessageLoop::current()->Quit(); | 
 |         quit_ = true; | 
 |         if (paint_) { | 
 |           std::cout << kHome << kUp << "    quitting" << kClearLine << std::endl | 
 |                     << kClearLine << std::endl; | 
 |         } else { | 
 |           std::cout << "    quitting" << std::endl; | 
 |         } | 
 |         break; | 
 |       case 'n': | 
 |         if (++input_file_names_iter_ == input_file_names_.end()) { | 
 |           input_file_names_iter_ = input_file_names_.begin(); | 
 |         } | 
 |         CreateNewMediaTest(); | 
 |         break; | 
 |       case 'p': | 
 |         if (input_file_names_iter_ == input_file_names_.begin()) { | 
 |           input_file_names_iter_ = input_file_names_.end(); | 
 |         } | 
 |         input_file_names_iter_--; | 
 |         CreateNewMediaTest(); | 
 |         break; | 
 |       case '0': | 
 |       case '1': | 
 |       case '2': | 
 |       case '3': | 
 |       case '4': | 
 |       case '5': | 
 |       case '6': | 
 |       case '7': | 
 |       case '8': | 
 |       case '9': { | 
 |         int64_t position_ns = ((keystroke - '0') / 10.0) * duration_ns_; | 
 |         media_test_->Seek(position_ns); | 
 |         if (!paint_) { | 
 |           std::cout << "    seeking to " << std::fixed << std::setprecision(1) | 
 |                     << double(position_ns) / ns_per_second << " seconds  " | 
 |                     << std::endl; | 
 |         } | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // Toggles between play and pause (prepared) states. | 
 |   void TogglePlayPause() { | 
 |     switch (media_test_->state()) { | 
 |       case MediaState::PAUSED: | 
 |         media_test_->Play(); | 
 |         break; | 
 |       case MediaState::PLAYING: | 
 |         media_test_->Pause(); | 
 |         break; | 
 |       case MediaState::ENDED: | 
 |         if (input_file_names_.size() == 1) { | 
 |           // Replaying the only file. Reuse the same MediaTest instance. | 
 |           media_test_->Seek(0); | 
 |           media_test_->Play(); | 
 |         } else { | 
 |           // Starting a new file. Create a new MediaTest instance. | 
 |           CreateNewMediaTest(); | 
 |         } | 
 |         break; | 
 |       default: | 
 |         break; | 
 |     } | 
 |   } | 
 |  | 
 |   // Does any needed work and schedules itself to run again soon. Quits when | 
 |   // quit_ is true. | 
 |   void Poll() { | 
 |     MOJO_DCHECK(!quit_); | 
 |  | 
 |     char keystroke = Keystroke(); | 
 |     if (keystroke != 0) { | 
 |       HandleKeystroke(keystroke); | 
 |     } | 
 |  | 
 |     if (quit_) { | 
 |       // Eat the any additional keystrokes, which would otherwise make it to the | 
 |       // command shell. | 
 |       while (Keystroke() != 0) { | 
 |         // Do nothing. | 
 |       } | 
 |       return; | 
 |     } | 
 |  | 
 |     if (paint_) { | 
 |       std::cout << kHome << "    " << std::fixed << std::setprecision(1) | 
 |                 << double(media_test_->position_ns()) / ns_per_second | 
 |                 << " seconds  " << kClearLine << std::flush; | 
 |     } | 
 |  | 
 |     base::MessageLoop::current()->PostDelayedTask( | 
 |         FROM_HERE, base::Bind(&MediaTestApp::Poll, base::Unretained(this)), | 
 |         base::TimeDelta::FromMilliseconds(100)); | 
 |   } | 
 |  | 
 |   void PlayNext() { | 
 |     if (++input_file_names_iter_ == input_file_names_.end()) { | 
 |       input_file_names_iter_ = input_file_names_.begin(); | 
 |     } else { | 
 |       CreateNewMediaTest(); | 
 |     } | 
 |   } | 
 |  | 
 |   const char* clear_line() const { return paint_ ? kClearLine : ""; } | 
 |  | 
 |   mojo::ApplicationImpl* app_; | 
 |   std::unique_ptr<MediaTest> media_test_; | 
 |   std::deque<std::string> input_file_names_; | 
 |   decltype(input_file_names_.begin()) input_file_names_iter_; | 
 |   bool quit_ = false; | 
 |   bool paint_ = true; | 
 |   bool metadata_shown_ = false; | 
 |   uint64_t duration_ns_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(MediaTestApp); | 
 | }; | 
 |  | 
 | const char* MediaTestApp::kHome = "\r"; | 
 | const char* MediaTestApp::kClearLine = "\033[K"; | 
 | const char* MediaTestApp::kUp = "\033[A"; | 
 |  | 
 | }  // namespace examples | 
 | }  // namespace media | 
 | }  // namespace mojo | 
 |  | 
 | MojoResult MojoMain(MojoHandle application_request) { | 
 |   mojo::ApplicationRunnerChromium runner( | 
 |       new mojo::media::examples::MediaTestApp); | 
 |   return runner.Run(application_request); | 
 | } |