实验四 windows或java平台下的Media Player(6学时)
基本要求
l 实现media player,支持多种媒体类型的播放
l 视频:avi
l 音频:wav、midi
l 图象序列:图象浏览,幻灯
l 播放列表功能
l Generic list
l 自定义的复杂播放
Bonus
界面美观-skin,可更换?
更丰富的媒体类型:
MP3,WMA,MPEG4,JPEG,PNG,WMF,Flash……
这些附加媒体类型的播放可以使用控件
这个实验参考了网上的一些相关资料,https://github.com/Al-assad
在实现这个实验的时候用到了JavaFX,需要现在eclipse中安装JavaFX插件。安装完成之后,新建一个JavaFX Project。
最后的视频播放效果如下所示,这是从网易云音乐下载的周杰伦的《七里香》MV,效果还不错,可以调节音量和进度。
音频播放效果如下所示
主要代码如下所示
package application;
public class Log {
public static void i(String tag, String content) {
System.out.println("[INFO][" + tag + "]: " + content);
}
public static void e(String tag, String content) {
System.out.println("[ERROR][" + tag + "]: " + content);
}
}
package application;
import javafx.application.Application;
import javafx.scene.*;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.stage.*;
import player.*;
import java.io.*;
import java.net.*;
public class Main extends Application {
MyMediaPlayer player;
FileChooser fileChooser;
MenuBar menuBar;
BorderPane pane;
Group root;
public static final String[] IMAGE_POSTFIX = {
"bmp", "dib",
"png",
"jpeg", "jpg"
};
public static final String[] AUDIO_POSTFIX = {
"mp3",
"wav"
};
public static final String[] VIDEO_POSTFIX = {
"avi",
"mp4"
};
public static final String FILENAME = "69_output_2.mp4";
public static String getPostfix(File file) {
String filename = file.getName();
String ret;
if (filename == null || filename.length() == 0) {
ret = null;
} else {
char[] curName = filename.toCharArray();
int indexDot = -1;
for (int i = curName.length - 1; i >= 0; i--) {
if (curName[i] == '.') {
indexDot = i;
break;
}
}
if (indexDot == -1) {
ret = null;
} else {
ret = filename.substring(indexDot + 1);
}
}
return ret;
}
public static boolean isThisType(String postfix, String[] postfixList) {
for (String item: postfixList) {
if (postfix.compareToIgnoreCase(item) == 0) {
return true;
}
}
return false;
}
public class UnknownFileTypeException extends Exception {
public UnknownFileTypeException() {
super("Cannot determine the file type. ");
}
public UnknownFileTypeException(String message) {
super(message);
}
}
@Override
public void start(Stage primaryStage) throws Exception{
//添加菜单栏,用于打开文件
MenuItem open = new MenuItem("Open");
Menu menuFile = new Menu("File");
menuBar = new MenuBar();
menuFile.getItems().add(open);
menuBar.getMenus().add(menuFile);
fileChooser = new FileChooser();
open.setOnAction(new EventHandler<ActionEvent>(){
public void handle(ActionEvent e){
File file = fileChooser.showOpenDialog(primaryStage);
if (file != null){
try {
Log.i("File", file.toURI().toURL().toExternalForm());
String postfix = getPostfix(file);
if (postfix == null) {
throw new UnknownFileTypeException();
}
if (isThisType(postfix, IMAGE_POSTFIX)) {
//测试嵌入式调用
MyMediaPlayer.popup(file.toURI().toURL().toExternalForm());
} else if (isThisType(postfix, AUDIO_POSTFIX)) {
//测试嵌入式调用
MyMediaPlayer.popup(file.toURI().toURL().toExternalForm());
} else if (isThisType(postfix, VIDEO_POSTFIX)) {
//测试嵌入式调用
MyMediaPlayer.popup(file.toURI().toURL().toExternalForm());
} else {
throw new UnknownFileTypeException();
}
} catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
});
//创建测试窗口
root = new Group();
pane = new BorderPane();
root.getChildren().add(pane);
// HBox hbox = new HBox();
// hbox.setAlignment(Pos.CENTER);
// pane.setBottom(hbox);
// Button popup = new Button("Popup");
// Button popup2 = new Button("Popup small");
// hbox.getChildren().addAll(popup,popup2);
//System.out.println(System.getProperty("user.dir"));
// player = new SimpleMediaPlayer();
pane.setTop(menuBar); //Set bar at the top
//测试弹窗式调用
// popup.setOnAction((ActionEvent e)->{
// SimpleMediaPlayer.popup(getClass().getResource(FILENAME).toString());
// });
// popup2.setOnAction((ActionEvent e)->{
// SimpleMediaPlayer.popup(getClass().getResource(FILENAME).toString(),550,400);
// });
primaryStage.setScene(new Scene(root, 300, 100, Color.WHITESMOKE));
primaryStage.setTitle("My Media Player");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
package player;
import javafx.event.*;
import javafx.fxml.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.stage.*;
import java.io.*;
public class MyMediaPlayer extends AnchorPane{
//TODO 修改player.fxml 使其为自适应的大小,使用AnchorBar或者修改底部工具栏高度
private static MyMediaPlayer myMediaPlayer; //创建实例保存到私有域中
private PlayerController controller; //储存每个实例的控制器对象
protected PlayerController getController(){ //提供控制器对象的调用接口
return this.controller;
}
//构造函数私有,实例保存在静态域,只向外部提供静态调用
private MyMediaPlayer(String mediaUrl){
try {
FXMLLoader fxmlloader = new FXMLLoader(getClass().getResource("player.fxml"));
Parent root = fxmlloader.load(); //将fxml节点添加到根节点中
controller = fxmlloader.getController();
this.getChildren().add(root); //主类节点加入根节点
} catch (IOException e) {
e.printStackTrace();
}
}
//TODO setSize失效
//设置播放器大小:暂不支持 popup 产生的实例调用该方法
public void setSize(int width,int height){
if(myMediaPlayer.getController().getPopup())
return ;
myMediaPlayer.getController().setMediaPlayer(width,height);
}
//实例化调用:默认大小500*400
public static MyMediaPlayer newInstance(String mediaUrl){
return newInstance(mediaUrl,600,400);
}
public static MyMediaPlayer newInstance(String mediaUrl,int width,int height){
myMediaPlayer = new MyMediaPlayer(mediaUrl);
myMediaPlayer.getController().start(mediaUrl,false,width,height); //非窗口化启动播放器控件
return myMediaPlayer;
}
//弹窗式调用:默认大小800*600
public static MyMediaPlayer popup(String mediaUrl){
return popup(mediaUrl,800,600);
}
public static MyMediaPlayer popup(String mediaUrl,int width,int height){
myMediaPlayer = new MyMediaPlayer(mediaUrl);
myMediaPlayer.getController().start(mediaUrl,true,width,height);
Scene scene = new Scene(myMediaPlayer,width,height);
myMediaPlayer.getController().setScene(scene);
Stage primaryStage = new Stage();
primaryStage.setTitle("Media Player");
primaryStage.setScene(scene);
//检测弹出窗口关闭事件,手动销毁simpleMediaPlayer对象;
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>(){
@Override
public void handle(WindowEvent event) {
myMediaPlayer.getController().destroy();
}
});
primaryStage.show();
return myMediaPlayer;
}
}
package player;
import javafx.application.*;
import javafx.beans.value.*;
import javafx.event.*;
import javafx.fxml.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.scene.media.*;
import javafx.stage.*;
import javafx.util.*;
public class PlayerController {
@FXML Button playBT;
@FXML Button stopBT;
@FXML Button maxBT;
@FXML Button volumeBT;
@FXML Label timeLB;
@FXML Slider processSD;
@FXML Slider volumeSD;
@FXML MediaView mediaView;
@FXML VBox controlBar;
@FXML BorderPane mediaPane;
@FXML AnchorPane anchorPane;
//控件素材图片
private String playIcon = getClass().getResource("icon/play.png").toString();
private String pauseIcon = getClass().getResource("icon/pause.png").toString();
private String stopIcon = getClass().getResource("icon/stop.png").toString();
private String volOffIcon = getClass().getResource("icon/volume_off.png").toString();
private String volOnIcon = getClass().getResource("icon/volume_On.png").toString();
private String maxIcon = getClass().getResource("icon/max.png").toString();
private MediaPlayer mediaPlayer;
private Media media;
private String url; //资源的url地址
private boolean popup; //窗口弹出方式
private Scene scene ; //父类窗口
private boolean atEndOfMedia = false; //记录视频是否处播放到结束
private final boolean repeat = false; //记录视频是否重复播放
private double volumeValue; //储存静音操作前的音量数据
private Duration duration ; //记录视频持续时间
private int mediaHeight; //视频资源的尺寸
private int mediaWidth;
private int currentHeight; //当前整个播放器的尺寸
private int currentWidth;
public void setScene(Scene scene){
this.scene = scene;
}
//程序初始化:设置按钮图标
public void initialize(){
//设置各控件图标
setIcon(playBT,playIcon,25);
setIcon(stopBT,stopIcon,25);
setIcon(volumeBT,volOnIcon,15);
setIcon(maxBT,maxIcon,25);
}
//程序启动项,传入必要参数
public void start(String url,boolean popup,int width,int height){
this.url = url;
this.popup = popup;
//MediaView设置
media = new Media(url);
mediaPlayer = new MediaPlayer(media);
mediaView.setMediaPlayer(mediaPlayer);
//设置播放器,在媒体资源加载完毕后,获取相应的数据,设置组件自适应布局
setMediaPlayer(width,height);
//设置各组件动作事件
setMediaViewOnClick();
setPlayButton();
setStopButton();
setVolumeButton();
setVolumeSD();
setProcessSlider();
setMaximizeButton();
}
//设置mediaPlayer(参数:整个播放器的尺寸)
void setMediaPlayer(int width,int height){
mediaPlayer.setCycleCount(repeat ? MediaPlayer.INDEFINITE : 1);
//视频就绪时更新 进度条 、时间标签、音量条数据,设置布局尺寸
mediaPlayer.setOnReady(new Runnable(){
@Override
public void run() {
duration = mediaPlayer.getMedia().getDuration();
volumeValue = mediaPlayer.getVolume();
mediaHeight = media.getHeight();
mediaWidth = media.getWidth();
//设置布局尺寸
setSize(width,height);
//设置尺寸随窗口改变自适应变化(只使用于弹窗)
if (scene!= null) {
scene.widthProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
setSize(newValue.intValue(),currentHeight);
}
});
scene.heightProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
setSize(currentWidth,newValue.intValue());
}
});
}
//设置全屏时的UI变化:工具栏只有在鼠标进入MediaView时才出现
EventHandler onScreen = new EventHandler<InputEvent>(){
@Override
public void handle(InputEvent event) {
controlBar.setVisible(true);
}
};
EventHandler offScreen = new EventHandler<InputEvent>(){
@Override
public void handle(InputEvent event) {
controlBar.setVisible(false);
}
};
if(scene != null && popup){
((Stage)scene.getWindow()).fullScreenProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue.booleanValue()) {
controlBar.setVisible(false);
mediaPane.addEventHandler(MouseEvent.MOUSE_CLICKED, onScreen);
controlBar.addEventHandler(MouseEvent.MOUSE_EXITED, offScreen);
}else{
controlBar.setVisible(true);
mediaPane.removeEventHandler(MouseEvent.MOUSE_CLICKED,onScreen);
controlBar.removeEventHandler(MouseEvent.MOUSE_EXITED,offScreen);
}
}
});
}
updateValues();
}
});
//mediaPlayer当前进度发生改变时候,进度条 、时间标签、音量条数据
mediaPlayer.currentTimeProperty().addListener(new ChangeListener<Duration>(){
@Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
updateValues();
}
});
}
//设置点击MediaView时暂停或开始
private void setMediaViewOnClick(){
mediaView.setOnMouseClicked(event -> {
if(media == null)
return;
MediaPlayer.Status status = mediaPlayer.getStatus();
if(status == MediaPlayer.Status.UNKNOWN || status == MediaPlayer.Status.HALTED ){
return;
}
//当资源处于暂停或停止状态时
if(status == MediaPlayer.Status.PAUSED || status == MediaPlayer.Status.READY || status == MediaPlayer.Status.STOPPED){
//当资源播放结束时,重绕资源
if(atEndOfMedia){
mediaPlayer.seek(mediaPlayer.getStartTime());
atEndOfMedia = false;
}
mediaPlayer.play();
setIcon(playBT,pauseIcon,25);
}else{ //当资源处于播放状态时
mediaPlayer.pause();
setIcon(playBT,playIcon,25);
}
});
}
//设置播放按钮动作
private void setPlayButton(){
playBT.setOnAction((ActionEvent e)->{
if(media == null)
return;
MediaPlayer.Status status = mediaPlayer.getStatus();
if(status == MediaPlayer.Status.UNKNOWN || status == MediaPlayer.Status.HALTED ){
return;
}
//当资源处于暂停或停止状态时
if(status == MediaPlayer.Status.PAUSED || status == MediaPlayer.Status.READY || status == MediaPlayer.Status.STOPPED){
//当资源播放结束时,重绕资源
if(atEndOfMedia){
mediaPlayer.seek(mediaPlayer.getStartTime());
atEndOfMedia = false;
}
mediaPlayer.play();
setIcon(playBT,pauseIcon,25);
}else{ //当资源处于播放状态时
mediaPlayer.pause();
setIcon(playBT,playIcon,25);
}
});
}
//设置停止按钮动作
private void setStopButton(){
stopBT.setOnAction((ActionEvent e )->{
if(media == null)
return;
mediaPlayer.stop();
setIcon(playBT,playIcon,25);
} );
}
//设置视频进度条动作
private void setProcessSlider(){
processSD.valueProperty().addListener(new ChangeListener<Number>(){
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(processSD.isValueChanging()){ //加入Slider正在改变的判定,否则由于update线程的存在,mediaPlayer会不停地回绕
mediaPlayer.seek(duration.multiply(processSD.getValue()/100.0));
}
}
});
}
//设置最大化按钮动作
public void setMaximizeButton(){
maxBT.setOnAction((ActionEvent e)->{
if(popup){
((Stage)scene.getWindow()).setFullScreen(true);
}else{
mediaPlayer.pause();
setIcon(playBT,pauseIcon,25);
MyMediaPlayer player = MyMediaPlayer.popup(url);
player.getController().getMediaPlayer().seek(this.mediaPlayer.getCurrentTime());
}
});
}
//设置音量按钮动作
private void setVolumeButton(){
volumeBT.setOnAction((ActionEvent e)->{
if(media == null)
return;
if(mediaPlayer.getVolume()>0){
volumeValue = mediaPlayer.getVolume();
volumeSD.setValue(0);
setIcon(volumeBT,volOffIcon,25);
}else{
mediaPlayer.setVolume(volumeValue);
volumeSD.setValue(volumeValue * 100);
setIcon(volumeBT,volOnIcon,15);
}
});
}
//设置音量滑条动作
private void setVolumeSD(){
volumeSD.valueProperty().addListener(new ChangeListener<Number>(){
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
mediaPlayer.setVolume(newValue.doubleValue()/100);
}
});
}
//更新视频数据(进度条 、时间标签、音量条数据)
protected void updateValues(){
if(processSD != null && timeLB!=null && volumeSD != null && volumeBT != null){
Platform.runLater(new Runnable(){
@Override
public void run() {
Duration currentTime = mediaPlayer.getCurrentTime();
timeLB.setText(formatTime(currentTime,duration)); //设置时间标签
processSD.setDisable(duration.isUnknown()); //无法读取时间是隐藏进度条
if(!processSD.isDisabled() && duration.greaterThan(Duration.ZERO) && !processSD.isValueChanging()){
processSD.setValue(currentTime.toMillis()/duration.toMillis() * 100); //设置进度条
}
if(!volumeSD.isValueChanging()){
volumeSD.setValue((int)Math.round(mediaPlayer.getVolume() *100)); //设置音量条
if(mediaPlayer.getVolume() == 0){ //设置音量按钮
setIcon(volumeBT,volOffIcon,20);
}else{
setIcon(volumeBT,volOnIcon,20);
}
}
}
});
}
}
//将Duration数据格式化,用于播放时间标签
protected String formatTime(Duration elapsed,Duration duration){
//将两个Duartion参数转化为 hh:mm:ss的形式后输出
int intElapsed = (int)Math.floor(elapsed.toSeconds());
int elapsedHours = intElapsed / (60 * 60);
int elapsedMinutes = (intElapsed - elapsedHours *60 *60)/ 60;
int elapsedSeconds = intElapsed - elapsedHours * 60 * 60 - elapsedMinutes * 60;
if(duration.greaterThan(Duration.ZERO)){
int intDuration = (int)Math.floor(duration.toSeconds());
int durationHours = intDuration / (60 * 60);
int durationMinutes = (intDuration - durationHours *60 * 60) / 60;
int durationSeconds = intDuration - durationHours * 60 * 60 - durationMinutes * 60;
if(durationHours > 0){
return String.format("%02d:%02d:%02d / %02d:%02d:%02d",elapsedHours,elapsedMinutes,elapsedSeconds,durationHours,durationMinutes,durationSeconds);
}else{
return String.format("%02d:%02d / %02d:%02d",elapsedMinutes,elapsedSeconds,durationMinutes,durationSeconds);
}
}else{
if(elapsedHours > 0){
return String.format("%02d:%02d:%02d / %02d:%02d:%02d",elapsedHours,elapsedMinutes,elapsedSeconds);
}else{
return String.format("%02d:%02d / %02d:%02d",elapsedMinutes,elapsedSeconds);
}
}
}
//为按钮获取图标
private void setIcon(Button button,String path,int size){
Image icon = new Image(path);
ImageView imageView = new ImageView(icon);
imageView.setFitWidth(size);
imageView.setFitHeight((int)(size * icon.getHeight() / icon.getWidth()));
button.setGraphic(imageView);
//设置图标点击时发亮
ColorAdjust colorAdjust = new ColorAdjust();
button.setOnMousePressed(event -> {
colorAdjust.setBrightness(0.5);
button.setEffect(colorAdjust);
});
button.setOnMouseReleased(event -> {
colorAdjust.setBrightness(0);
button.setEffect(colorAdjust);
});
}
public MediaPlayer getMediaPlayer(){
return this.mediaPlayer;
}
//设置关闭窗口时的动作,手动释放资源,回收内存
public void destroy(){
if(mediaPlayer.getStatus() == MediaPlayer.Status.PLAYING){
mediaPlayer.stop();
}
mediaPlayer.dispose(); //释放meidaPlayer的Media资源
media = null;
mediaPlayer = null;
System.gc(); //通知JVM垃圾回收器
}
//设置播放器尺寸
public void setSize(int width,int height){
currentWidth = width;
currentHeight = height;
setUISuitable();
}
//UI控件自适应大小
private void setUISuitable(){
anchorPane.setPrefSize(currentWidth,currentHeight);
anchorPane.setBottomAnchor(controlBar, 0.0); //设置控制条位置
anchorPane.setTopAnchor(mediaPane,((double)currentHeight - (double)currentWidth *(double)mediaHeight / (double)mediaWidth - 50)/2); //设置视频面板位置
mediaView.setFitWidth(currentWidth); //设置MediaView尺寸
mediaView.setFitHeight((double)currentWidth*(double)mediaHeight / (double)mediaHeight);
controlBar.setPrefWidth(currentWidth); //设置工具条宽度
}
public boolean getPopup(){
return this.popup;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.media.MediaView?>
<AnchorPane fx:id="anchorPane" style="-fx-background-color: #3c3c3c;" xmlns="http://javafx.com/javafx/8.0.92" xmlns:fx="http://javafx.com/fxml/1" fx:controller="player.PlayerController">
<children>
<BorderPane fx:id="mediaPane">
<center>
<MediaView fx:id="mediaView" fitHeight="530.0" fitWidth="800.0" nodeOrientation="INHERIT" />
</center>
</BorderPane>
<VBox fx:id="controlBar" alignment="BOTTOM_CENTER" maxHeight="-Infinity" minHeight="-Infinity" prefHeight="70.0" style="-fx-background-color: #636363;">
<children>
<Slider fx:id="processSD" style="-fx-background-color: #4b4b4b;" VBox.vgrow="ALWAYS" />
<BorderPane fx:id="controlBorderPane" maxHeight="-Infinity" minHeight="-Infinity" prefHeight="50.0">
<center>
<HBox alignment="CENTER" spacing="20.0" BorderPane.alignment="CENTER">
<children>
<Button fx:id="playBT" mnemonicParsing="false" style="-fx-background-color: #636363;" HBox.hgrow="ALWAYS" />
<Button fx:id="stopBT" mnemonicParsing="false" style="-fx-background-color: #636363;" HBox.hgrow="ALWAYS" />
</children>
</HBox>
</center>
<left>
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" spacing="20.0" BorderPane.alignment="CENTER">
<children>
<Button fx:id="maxBT" mnemonicParsing="false" style="-fx-background-color: #636363;" HBox.hgrow="ALWAYS" />
<Label fx:id="timeLB" text="Time" textFill="#dadada" HBox.hgrow="ALWAYS" />
</children>
<BorderPane.margin>
<Insets left="30.0" />
</BorderPane.margin>
</HBox>
</left>
<right>
<HBox alignment="CENTER_RIGHT" minHeight="-Infinity" prefWidth="200.0" spacing="10.0" BorderPane.alignment="CENTER">
<children>
<Button fx:id="volumeBT" mnemonicParsing="false" style="-fx-background-color: #636363;" HBox.hgrow="ALWAYS" />
<Slider fx:id="volumeSD" minHeight="-Infinity" prefWidth="150.0" HBox.hgrow="ALWAYS" />
</children>
<padding>
<Insets right="30.0" />
</padding>
</HBox>
</right>
</BorderPane>
</children>
</VBox>
</children>
</AnchorPane>