25bd5d8adb
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
709 lines
26 KiB
C++
709 lines
26 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE examples.
|
|
Copyright (c) 2020 - Raw Material Software Limited
|
|
|
|
The code included in this file is provided under the terms of the ISC license
|
|
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
|
To use, copy, modify, and/or distribute this software for any purpose with or
|
|
without fee is hereby granted provided that the above copyright notice and
|
|
this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
|
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
|
PURPOSE, ARE DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*******************************************************************************
|
|
The block below describes the properties of this PIP. A PIP is a short snippet
|
|
of code that can be read by the Projucer and used to generate a JUCE project.
|
|
|
|
BEGIN_JUCE_PIP_METADATA
|
|
|
|
name: VideoDemo
|
|
version: 1.0.0
|
|
vendor: JUCE
|
|
website: http://juce.com
|
|
description: Plays video files.
|
|
|
|
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
|
juce_gui_basics, juce_gui_extra, juce_video
|
|
exporters: xcode_mac, vs2019, androidstudio, xcode_iphone
|
|
|
|
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
|
|
|
type: Component
|
|
mainClass: VideoDemo
|
|
|
|
useLocalCopy: 1
|
|
|
|
END_JUCE_PIP_METADATA
|
|
|
|
*******************************************************************************/
|
|
|
|
#pragma once
|
|
|
|
#include "../Assets/DemoUtilities.h"
|
|
|
|
#if JUCE_MAC || JUCE_WINDOWS
|
|
//==============================================================================
|
|
// so that we can easily have two video windows each with a file browser, wrap this up as a class..
|
|
class MovieComponentWithFileBrowser : public Component,
|
|
public DragAndDropTarget,
|
|
private FilenameComponentListener
|
|
{
|
|
public:
|
|
MovieComponentWithFileBrowser()
|
|
: videoComp (true)
|
|
{
|
|
addAndMakeVisible (videoComp);
|
|
|
|
addAndMakeVisible (fileChooser);
|
|
fileChooser.addListener (this);
|
|
fileChooser.setBrowseButtonText ("browse");
|
|
}
|
|
|
|
void setFile (const File& file)
|
|
{
|
|
fileChooser.setCurrentFile (file, true);
|
|
}
|
|
|
|
void paintOverChildren (Graphics& g) override
|
|
{
|
|
if (isDragOver)
|
|
{
|
|
g.setColour (Colours::red);
|
|
g.drawRect (fileChooser.getBounds(), 2);
|
|
}
|
|
}
|
|
|
|
void resized() override
|
|
{
|
|
videoComp.setBounds (getLocalBounds().reduced (10));
|
|
}
|
|
|
|
bool isInterestedInDragSource (const SourceDetails&) override { return true; }
|
|
|
|
void itemDragEnter (const SourceDetails&) override
|
|
{
|
|
isDragOver = true;
|
|
repaint();
|
|
}
|
|
|
|
void itemDragExit (const SourceDetails&) override
|
|
{
|
|
isDragOver = false;
|
|
repaint();
|
|
}
|
|
|
|
void itemDropped (const SourceDetails& dragSourceDetails) override
|
|
{
|
|
setFile (dragSourceDetails.description.toString());
|
|
isDragOver = false;
|
|
repaint();
|
|
}
|
|
|
|
private:
|
|
VideoComponent videoComp;
|
|
|
|
bool isDragOver = false;
|
|
FilenameComponent fileChooser { "movie", {}, true, false, false, "*", {}, "(choose a video file to play)"};
|
|
|
|
void filenameComponentChanged (FilenameComponent*) override
|
|
{
|
|
auto url = URL (fileChooser.getCurrentFile());
|
|
|
|
// this is called when the user changes the filename in the file chooser box
|
|
auto result = videoComp.load (url);
|
|
videoLoadingFinished (url, result);
|
|
}
|
|
|
|
void videoLoadingFinished (const URL& url, Result result)
|
|
{
|
|
ignoreUnused (url);
|
|
|
|
if (result.wasOk())
|
|
{
|
|
// loaded the file ok, so let's start it playing..
|
|
|
|
videoComp.play();
|
|
resized(); // update to reflect the video's aspect ratio
|
|
}
|
|
else
|
|
{
|
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
|
"Couldn't load the file!",
|
|
result.getErrorMessage());
|
|
}
|
|
}
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MovieComponentWithFileBrowser)
|
|
};
|
|
|
|
//==============================================================================
|
|
class VideoDemo : public Component,
|
|
public DragAndDropContainer,
|
|
private FileBrowserListener
|
|
{
|
|
public:
|
|
VideoDemo()
|
|
{
|
|
setOpaque (true);
|
|
|
|
movieList.setDirectory (File::getSpecialLocation (File::userMoviesDirectory), true, true);
|
|
directoryThread.startThread (1);
|
|
|
|
fileTree.setTitle ("Files");
|
|
fileTree.addListener (this);
|
|
fileTree.setColour (FileTreeComponent::backgroundColourId, Colours::lightgrey.withAlpha (0.6f));
|
|
addAndMakeVisible (fileTree);
|
|
|
|
addAndMakeVisible (resizerBar);
|
|
|
|
loadLeftButton .onClick = [this] { movieCompLeft .setFile (fileTree.getSelectedFile (0)); };
|
|
loadRightButton.onClick = [this] { movieCompRight.setFile (fileTree.getSelectedFile (0)); };
|
|
|
|
addAndMakeVisible (loadLeftButton);
|
|
addAndMakeVisible (loadRightButton);
|
|
|
|
addAndMakeVisible (movieCompLeft);
|
|
addAndMakeVisible (movieCompRight);
|
|
|
|
// we have to set up our StretchableLayoutManager so it know the limits and preferred sizes of it's contents
|
|
stretchableManager.setItemLayout (0, // for the fileTree
|
|
-0.1, -0.9, // must be between 50 pixels and 90% of the available space
|
|
-0.3); // and its preferred size is 30% of the total available space
|
|
|
|
stretchableManager.setItemLayout (1, // for the resize bar
|
|
5, 5, 5); // hard limit to 5 pixels
|
|
|
|
stretchableManager.setItemLayout (2, // for the movie components
|
|
-0.1, -0.9, // size must be between 50 pixels and 90% of the available space
|
|
-0.7); // and its preferred size is 70% of the total available space
|
|
|
|
setSize (500, 500);
|
|
}
|
|
|
|
~VideoDemo() override
|
|
{
|
|
fileTree.removeListener (this);
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
|
}
|
|
|
|
void resized() override
|
|
{
|
|
// make a list of two of our child components that we want to reposition
|
|
Component* comps[] = { &fileTree, &resizerBar, nullptr };
|
|
|
|
// this will position the 3 components, one above the other, to fit
|
|
// vertically into the rectangle provided.
|
|
stretchableManager.layOutComponents (comps, 3,
|
|
0, 0, getWidth(), getHeight(),
|
|
true, true);
|
|
|
|
// now position out two video components in the space that's left
|
|
auto area = getLocalBounds().removeFromBottom (getHeight() - resizerBar.getBottom());
|
|
|
|
{
|
|
auto buttonArea = area.removeFromTop (30);
|
|
loadLeftButton .setBounds (buttonArea.removeFromLeft (buttonArea.getWidth() / 2).reduced (5));
|
|
loadRightButton.setBounds (buttonArea.reduced (5));
|
|
}
|
|
|
|
movieCompLeft .setBounds (area.removeFromLeft (area.getWidth() / 2).reduced (5));
|
|
movieCompRight.setBounds (area.reduced (5));
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<FileChooser> fileChooser;
|
|
WildcardFileFilter moviesWildcardFilter { "*", "*", "Movies File Filter" };
|
|
TimeSliceThread directoryThread { "Movie File Scanner Thread" };
|
|
DirectoryContentsList movieList { &moviesWildcardFilter, directoryThread };
|
|
FileTreeComponent fileTree { movieList };
|
|
|
|
StretchableLayoutManager stretchableManager;
|
|
StretchableLayoutResizerBar resizerBar { &stretchableManager, 1, false };
|
|
|
|
TextButton loadLeftButton { "Load Left" },
|
|
loadRightButton { "Load Right" };
|
|
MovieComponentWithFileBrowser movieCompLeft, movieCompRight;
|
|
|
|
void selectionChanged() override
|
|
{
|
|
// we're just going to update the drag description of out tree so that rows can be dragged onto the file players
|
|
fileTree.setDragAndDropDescription (fileTree.getSelectedFile().getFullPathName());
|
|
}
|
|
|
|
void fileClicked (const File&, const MouseEvent&) override {}
|
|
void fileDoubleClicked (const File&) override {}
|
|
void browserRootChanged (const File&) override {}
|
|
|
|
void selectVideoFile()
|
|
{
|
|
fileChooser.reset (new FileChooser ("Choose a file to open...", File::getCurrentWorkingDirectory(),
|
|
"*", false));
|
|
|
|
fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
|
|
[this] (const FileChooser& chooser)
|
|
{
|
|
String chosen;
|
|
auto results = chooser.getURLResults();
|
|
|
|
// TODO: support non local files too
|
|
if (results.size() > 0)
|
|
movieCompLeft.setFile (results[0].getLocalFile());
|
|
});
|
|
}
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoDemo)
|
|
};
|
|
#elif JUCE_IOS || JUCE_ANDROID
|
|
//==============================================================================
|
|
class VideoDemo : public Component,
|
|
private Timer
|
|
{
|
|
public:
|
|
VideoDemo()
|
|
: videoCompWithNativeControls (true),
|
|
videoCompNoNativeControls (false)
|
|
{
|
|
loadLocalButton .onClick = [this] { selectVideoFile(); };
|
|
loadUrlButton .onClick = [this] { showVideoUrlPrompt(); };
|
|
seekToStartButton.onClick = [this] { seekVideoToStart(); };
|
|
playButton .onClick = [this] { playVideo(); };
|
|
pauseButton .onClick = [this] { pauseVideo(); };
|
|
unloadButton .onClick = [this] { unloadVideoFile(); };
|
|
|
|
volumeLabel .setColour (Label::textColourId, Colours::white);
|
|
currentPositionLabel.setColour (Label::textColourId, Colours::white);
|
|
|
|
volumeLabel .setJustificationType (Justification::right);
|
|
currentPositionLabel.setJustificationType (Justification::right);
|
|
|
|
volumeSlider .setRange (0.0, 1.0);
|
|
positionSlider.setRange (0.0, 1.0);
|
|
|
|
volumeSlider .setSliderSnapsToMousePosition (false);
|
|
positionSlider.setSliderSnapsToMousePosition (false);
|
|
|
|
volumeSlider.setSkewFactor (1.5);
|
|
volumeSlider.setValue (1.0, dontSendNotification);
|
|
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
|
curVideoComp->onGlobalMediaVolumeChanged = [this]() { volumeSlider.setValue (curVideoComp->getAudioVolume(), dontSendNotification); };
|
|
#endif
|
|
|
|
volumeSlider .onValueChange = [this]() { curVideoComp->setAudioVolume ((float) volumeSlider.getValue()); };
|
|
positionSlider.onValueChange = [this]() { seekVideoToNormalisedPosition (positionSlider.getValue()); };
|
|
|
|
positionSlider.onDragStart = [this]()
|
|
{
|
|
positionSliderDragging = true;
|
|
wasPlayingBeforeDragStart = curVideoComp->isPlaying();
|
|
|
|
if (wasPlayingBeforeDragStart)
|
|
curVideoComp->stop();
|
|
};
|
|
|
|
positionSlider.onDragEnd = [this]()
|
|
{
|
|
if (wasPlayingBeforeDragStart)
|
|
curVideoComp->play();
|
|
|
|
wasPlayingBeforeDragStart = false;
|
|
|
|
// Ensure the slider does not temporarily jump back on consecutive timer callback.
|
|
Timer::callAfterDelay (500, [this]() { positionSliderDragging = false; });
|
|
};
|
|
|
|
playSpeedComboBox.addItem ("25%", 25);
|
|
playSpeedComboBox.addItem ("50%", 50);
|
|
playSpeedComboBox.addItem ("100%", 100);
|
|
playSpeedComboBox.addItem ("200%", 200);
|
|
playSpeedComboBox.addItem ("400%", 400);
|
|
playSpeedComboBox.setSelectedId (100, dontSendNotification);
|
|
playSpeedComboBox.onChange = [this]() { curVideoComp->setPlaySpeed (playSpeedComboBox.getSelectedId() / 100.0); };
|
|
|
|
setTransportControlsEnabled (false);
|
|
|
|
addAndMakeVisible (loadLocalButton);
|
|
addAndMakeVisible (loadUrlButton);
|
|
addAndMakeVisible (volumeLabel);
|
|
addAndMakeVisible (volumeSlider);
|
|
addChildComponent (videoCompWithNativeControls);
|
|
addChildComponent (videoCompNoNativeControls);
|
|
addAndMakeVisible (positionSlider);
|
|
addAndMakeVisible (currentPositionLabel);
|
|
|
|
addAndMakeVisible (playSpeedComboBox);
|
|
addAndMakeVisible (seekToStartButton);
|
|
addAndMakeVisible (playButton);
|
|
addAndMakeVisible (unloadButton);
|
|
addChildComponent (pauseButton);
|
|
|
|
setSize (500, 500);
|
|
|
|
RuntimePermissions::request (RuntimePermissions::readExternalStorage,
|
|
[] (bool granted)
|
|
{
|
|
if (! granted)
|
|
{
|
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
|
"Permissions warning",
|
|
"External storage access permission not granted, some files"
|
|
" may be inaccessible.");
|
|
}
|
|
});
|
|
|
|
setPortraitOrientationEnabled (true);
|
|
}
|
|
|
|
~VideoDemo() override
|
|
{
|
|
curVideoComp->onPlaybackStarted = nullptr;
|
|
curVideoComp->onPlaybackStopped = nullptr;
|
|
curVideoComp->onErrorOccurred = nullptr;
|
|
curVideoComp->onGlobalMediaVolumeChanged = nullptr;
|
|
|
|
setPortraitOrientationEnabled (false);
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
|
}
|
|
|
|
void resized() override
|
|
{
|
|
auto area = getLocalBounds();
|
|
|
|
int marginSize = 5;
|
|
int buttonHeight = 20;
|
|
|
|
area.reduce (0, marginSize);
|
|
|
|
auto topArea = area.removeFromTop (buttonHeight);
|
|
loadLocalButton.setBounds (topArea.removeFromLeft (topArea.getWidth() / 6));
|
|
loadUrlButton.setBounds (topArea.removeFromLeft (loadLocalButton.getWidth()));
|
|
volumeLabel.setBounds (topArea.removeFromLeft (loadLocalButton.getWidth()));
|
|
volumeSlider.setBounds (topArea.reduced (10, 0));
|
|
|
|
auto transportArea = area.removeFromBottom (buttonHeight);
|
|
auto positionArea = area.removeFromBottom (buttonHeight).reduced (marginSize, 0);
|
|
|
|
playSpeedComboBox.setBounds (transportArea.removeFromLeft (jmax (50, transportArea.getWidth() / 5)));
|
|
|
|
auto controlWidth = transportArea.getWidth() / 3;
|
|
|
|
currentPositionLabel.setBounds (positionArea.removeFromRight (jmax (150, controlWidth)));
|
|
positionSlider.setBounds (positionArea);
|
|
|
|
seekToStartButton.setBounds (transportArea.removeFromLeft (controlWidth));
|
|
playButton .setBounds (transportArea.removeFromLeft (controlWidth));
|
|
unloadButton .setBounds (transportArea.removeFromLeft (controlWidth));
|
|
pauseButton.setBounds (playButton.getBounds());
|
|
|
|
area.removeFromTop (marginSize);
|
|
area.removeFromBottom (marginSize);
|
|
|
|
videoCompWithNativeControls.setBounds (area);
|
|
videoCompNoNativeControls.setBounds (area);
|
|
|
|
if (positionSlider.getWidth() > 0)
|
|
positionSlider.setMouseDragSensitivity (positionSlider.getWidth());
|
|
}
|
|
|
|
private:
|
|
TextButton loadLocalButton { "Load Local" };
|
|
TextButton loadUrlButton { "Load URL" };
|
|
Label volumeLabel { "volumeLabel", "Vol:" };
|
|
Slider volumeSlider { Slider::LinearHorizontal, Slider::NoTextBox };
|
|
|
|
VideoComponent videoCompWithNativeControls;
|
|
VideoComponent videoCompNoNativeControls;
|
|
#if JUCE_IOS || JUCE_MAC
|
|
VideoComponent* curVideoComp = &videoCompWithNativeControls;
|
|
#else
|
|
VideoComponent* curVideoComp = &videoCompNoNativeControls;
|
|
#endif
|
|
bool isFirstSetup = true;
|
|
|
|
Slider positionSlider { Slider::LinearHorizontal, Slider::NoTextBox };
|
|
bool positionSliderDragging = false;
|
|
bool wasPlayingBeforeDragStart = false;
|
|
|
|
Label currentPositionLabel { "currentPositionLabel", "-:- / -:-" };
|
|
|
|
ComboBox playSpeedComboBox { "playSpeedComboBox" };
|
|
TextButton seekToStartButton { "|<" };
|
|
TextButton playButton { "Play" };
|
|
TextButton pauseButton { "Pause" };
|
|
TextButton unloadButton { "Unload" };
|
|
|
|
std::unique_ptr<FileChooser> fileChooser;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoDemo)
|
|
JUCE_DECLARE_WEAK_REFERENCEABLE (VideoDemo)
|
|
|
|
//==============================================================================
|
|
void setPortraitOrientationEnabled (bool shouldBeEnabled)
|
|
{
|
|
auto allowedOrientations = Desktop::getInstance().getOrientationsEnabled();
|
|
|
|
if (shouldBeEnabled)
|
|
allowedOrientations |= Desktop::upright;
|
|
else
|
|
allowedOrientations &= ~Desktop::upright;
|
|
|
|
Desktop::getInstance().setOrientationsEnabled (allowedOrientations);
|
|
}
|
|
|
|
void setTransportControlsEnabled (bool shouldBeEnabled)
|
|
{
|
|
positionSlider .setEnabled (shouldBeEnabled);
|
|
playSpeedComboBox.setEnabled (shouldBeEnabled);
|
|
seekToStartButton.setEnabled (shouldBeEnabled);
|
|
playButton .setEnabled (shouldBeEnabled);
|
|
unloadButton .setEnabled (shouldBeEnabled);
|
|
pauseButton .setEnabled (shouldBeEnabled);
|
|
}
|
|
|
|
void selectVideoFile()
|
|
{
|
|
fileChooser.reset (new FileChooser ("Choose a video file to open...", File::getCurrentWorkingDirectory(),
|
|
"*", true));
|
|
|
|
fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
|
|
[this] (const FileChooser& chooser)
|
|
{
|
|
auto results = chooser.getURLResults();
|
|
|
|
if (results.size() > 0)
|
|
loadVideo (results[0]);
|
|
});
|
|
}
|
|
|
|
void loadVideo (const URL& url)
|
|
{
|
|
unloadVideoFile();
|
|
|
|
#if JUCE_IOS || JUCE_MAC
|
|
askIfUseNativeControls (url);
|
|
#else
|
|
loadUrl (url);
|
|
setupVideoComp (false);
|
|
#endif
|
|
}
|
|
|
|
void askIfUseNativeControls (const URL& url)
|
|
{
|
|
auto* aw = new AlertWindow ("Choose viewer type", {}, MessageBoxIconType::NoIcon);
|
|
|
|
aw->addButton ("Yes", 1, KeyPress (KeyPress::returnKey));
|
|
aw->addButton ("No", 0, KeyPress (KeyPress::escapeKey));
|
|
aw->addTextBlock ("Do you want to use the viewer with native controls?");
|
|
|
|
auto callback = ModalCallbackFunction::forComponent (videoViewerTypeChosen, this, url);
|
|
aw->enterModalState (true, callback, true);
|
|
}
|
|
|
|
static void videoViewerTypeChosen (int result, VideoDemo* owner, URL url)
|
|
{
|
|
if (owner != nullptr)
|
|
{
|
|
owner->setupVideoComp (result != 0);
|
|
owner->loadUrl (url);
|
|
}
|
|
}
|
|
|
|
void setupVideoComp (bool useNativeViewerWithNativeControls)
|
|
{
|
|
auto* oldVideoComp = curVideoComp;
|
|
|
|
if (useNativeViewerWithNativeControls)
|
|
curVideoComp = &videoCompWithNativeControls;
|
|
else
|
|
curVideoComp = &videoCompNoNativeControls;
|
|
|
|
if (isFirstSetup || oldVideoComp != curVideoComp)
|
|
{
|
|
oldVideoComp->onPlaybackStarted = nullptr;
|
|
oldVideoComp->onPlaybackStopped = nullptr;
|
|
oldVideoComp->onErrorOccurred = nullptr;
|
|
oldVideoComp->setVisible (false);
|
|
|
|
curVideoComp->onPlaybackStarted = [this]() { processPlaybackStarted(); };
|
|
curVideoComp->onPlaybackStopped = [this]() { processPlaybackPaused(); };
|
|
curVideoComp->onErrorOccurred = [this](const String& errorMessage) { errorOccurred (errorMessage); };
|
|
curVideoComp->setVisible (true);
|
|
|
|
#if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME
|
|
oldVideoComp->onGlobalMediaVolumeChanged = nullptr;
|
|
curVideoComp->onGlobalMediaVolumeChanged = [this]() { volumeSlider.setValue (curVideoComp->getAudioVolume(), dontSendNotification); };
|
|
#endif
|
|
}
|
|
|
|
isFirstSetup = false;
|
|
}
|
|
|
|
void loadUrl (const URL& url)
|
|
{
|
|
curVideoComp->loadAsync (url, [this] (const URL& u, Result r) { videoLoadingFinished (u, r); });
|
|
}
|
|
|
|
void showVideoUrlPrompt()
|
|
{
|
|
auto* aw = new AlertWindow ("Enter URL for video to load", {}, MessageBoxIconType::NoIcon);
|
|
|
|
aw->addButton ("OK", 1, KeyPress (KeyPress::returnKey));
|
|
aw->addButton ("Cancel", 0, KeyPress (KeyPress::escapeKey));
|
|
aw->addTextEditor ("videoUrlTextEditor", "https://www.rmp-streaming.com/media/bbb-360p.mp4");
|
|
|
|
auto callback = ModalCallbackFunction::forComponent (videoUrlPromptClosed, this, Component::SafePointer<AlertWindow> (aw));
|
|
aw->enterModalState (true, callback, true);
|
|
}
|
|
|
|
static void videoUrlPromptClosed (int result, VideoDemo* owner, Component::SafePointer<AlertWindow> aw)
|
|
{
|
|
if (result != 0 && owner != nullptr && aw != nullptr)
|
|
{
|
|
auto url = aw->getTextEditorContents ("videoUrlTextEditor");
|
|
|
|
if (url.isNotEmpty())
|
|
owner->loadVideo (url);
|
|
}
|
|
}
|
|
|
|
void videoLoadingFinished (const URL& url, Result result)
|
|
{
|
|
ignoreUnused (url);
|
|
|
|
if (result.wasOk())
|
|
{
|
|
resized(); // update to reflect the video's aspect ratio
|
|
|
|
setTransportControlsEnabled (true);
|
|
|
|
currentPositionLabel.setText (getPositionString (0.0, curVideoComp->getVideoDuration()), sendNotification);
|
|
positionSlider.setValue (0.0, dontSendNotification);
|
|
playSpeedComboBox.setSelectedId (100, dontSendNotification);
|
|
}
|
|
else
|
|
{
|
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
|
|
"Couldn't load the file!",
|
|
result.getErrorMessage());
|
|
}
|
|
}
|
|
|
|
static String getPositionString (double playPositionSeconds, double durationSeconds)
|
|
{
|
|
auto positionMs = static_cast<int> (1000 * playPositionSeconds);
|
|
int posMinutes = positionMs / 60000;
|
|
int posSeconds = (positionMs % 60000) / 1000;
|
|
int posMillis = positionMs % 1000;
|
|
|
|
auto totalMs = static_cast<int> (1000 * durationSeconds);
|
|
int totMinutes = totalMs / 60000;
|
|
int totSeconds = (totalMs % 60000) / 1000;
|
|
int totMillis = totalMs % 1000;
|
|
|
|
return String::formatted ("%02d:%02d:%03d / %02d:%02d:%03d",
|
|
posMinutes, posSeconds, posMillis,
|
|
totMinutes, totSeconds, totMillis);
|
|
}
|
|
|
|
void updatePositionSliderAndLabel()
|
|
{
|
|
auto position = curVideoComp->getPlayPosition();
|
|
auto duration = curVideoComp->getVideoDuration();
|
|
|
|
currentPositionLabel.setText (getPositionString (position, duration), sendNotification);
|
|
|
|
if (! positionSliderDragging)
|
|
positionSlider.setValue (duration != 0 ? (position / duration) : 0.0, dontSendNotification);
|
|
}
|
|
|
|
void seekVideoToStart()
|
|
{
|
|
seekVideoToNormalisedPosition (0.0);
|
|
}
|
|
|
|
void seekVideoToNormalisedPosition (double normalisedPos)
|
|
{
|
|
normalisedPos = jlimit (0.0, 1.0, normalisedPos);
|
|
|
|
auto duration = curVideoComp->getVideoDuration();
|
|
auto newPos = jlimit (0.0, duration, duration * normalisedPos);
|
|
|
|
curVideoComp->setPlayPosition (newPos);
|
|
currentPositionLabel.setText (getPositionString (newPos, curVideoComp->getVideoDuration()), sendNotification);
|
|
positionSlider.setValue (normalisedPos, dontSendNotification);
|
|
}
|
|
|
|
void playVideo()
|
|
{
|
|
curVideoComp->play();
|
|
}
|
|
|
|
void processPlaybackStarted()
|
|
{
|
|
playButton.setVisible (false);
|
|
pauseButton.setVisible (true);
|
|
|
|
startTimer (20);
|
|
}
|
|
|
|
void pauseVideo()
|
|
{
|
|
curVideoComp->stop();
|
|
}
|
|
|
|
void processPlaybackPaused()
|
|
{
|
|
// On seeking to a new pos, the playback may be temporarily paused.
|
|
if (positionSliderDragging)
|
|
return;
|
|
|
|
pauseButton.setVisible (false);
|
|
playButton.setVisible (true);
|
|
}
|
|
|
|
void errorOccurred (const String& errorMessage)
|
|
{
|
|
AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon,
|
|
"An error has occurred",
|
|
errorMessage + ", video will be unloaded.");
|
|
|
|
unloadVideoFile();
|
|
}
|
|
|
|
void unloadVideoFile()
|
|
{
|
|
curVideoComp->closeVideo();
|
|
|
|
setTransportControlsEnabled (false);
|
|
stopTimer();
|
|
|
|
pauseButton.setVisible (false);
|
|
playButton.setVisible (true);
|
|
|
|
currentPositionLabel.setText ("-:- / -:-", sendNotification);
|
|
positionSlider.setValue (0.0, dontSendNotification);
|
|
}
|
|
|
|
void timerCallback() override
|
|
{
|
|
updatePositionSliderAndLabel();
|
|
}
|
|
};
|
|
#elif JUCE_LINUX || JUCE_BSD
|
|
#error "This demo is not supported on Linux!"
|
|
#endif
|