Java使用javacv实现的多种音视频格式播放器
·
一、前言
最近写了一款图形界面版的音视频播放器,可以支持多种音视频格式的播放,比如MP4、avi、mkv、flv、MP3、ogg、wav等多种格式,非常好用,可以本地打开多种格式音视频。
二、实现
1.通过引入javacv相关依赖实现,如下:
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.9</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.9</version>
</dependency>
2.定义一个类VideoPlayer,实现播放功能,代码如下:
public class VideoPlayer {
private FFmpegFrameGrabber grabber;
private CanvasFrame canvasFrame;
private volatile boolean isPlaying = true; // 控制播放/暂停状态
private volatile boolean isScreen = false; // 放大至整个屏幕
private Thread playbackThread;
private JSlider progressBar;
private JButton playPauseButton; //播放或暂停按钮
private JButton openFileButton;
private JButton fullExitScreen;
private ImageIcon playImage;
private ImageIcon pauseImage;
private ImageIcon fullScreenImage;
private ImageIcon exitScreenImage;
private JLabel startTimeJl;
private JLabel durationJl;
private long totalDuration; // 视频总时长(毫秒)
private long maxTimestampUs;//最大帧时长(微秒)
private volatile long currentTime = 0; // 当前播放时间(毫秒)
private volatile long lastProgressUpdate = 0; // 上次更新进度条的时间
private volatile boolean isSelectFile = false; // 上次更新进度条的时间
private volatile boolean userDragging = false;
private String videoPath;
private static final long PROGRESS_UPDATE_INTERVAL = 1000; // 每秒更新一次进度条
private ReentrantLock lock = new ReentrantLock();
// 使用一个队列来缓存已解码的帧
LinkedHashMap<Integer,Frame> frameCacheMap = new LinkedHashMap<>();
public VideoPlayer(String videoPath,String title) {
// 初始化窗口
// 创建画布,用于显示帧
this.videoPath=videoPath;
canvasFrame = new CanvasFrame(title,1.00); // 创建画布,第二个参数为窗口的持续时间系数,1.0表示实时
canvasFrame.getRootPane().setWindowDecorationStyle(JRootPane.NONE);
canvasFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//canvasFrame.setLocationRelativeTo(null);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int x = (screenSize.width - 1000) / 2;
int y = (screenSize.height - 800) / 2;
canvasFrame.setLocation(x, y);
canvasFrame.setCanvasSize(1000,700);
canvasFrame.setSize(1000,800);
//canvasFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
//setFullScreen();
canvasFrame.setVisible(true);
// 创建一个画布用于显示视频帧
canvasFrame.setResizable(false);
// 创建播放/暂停按钮
playPauseButton = new JButton();
playPauseButton.setMargin(new Insets(0, 0, 0, 0)); // 设置边距为0
playPauseButton.setBorderPainted(false);
playPauseButton.setContentAreaFilled(false); // 禁用背景填充(透明背景)
playPauseButton.setFocusPainted(false); // 移除焦点框(可选)
playPauseButton.setMargin(new Insets(0, 0, 0, 0)); // 设置边距为0
fullExitScreen=new JButton();
fullExitScreen.setBorderPainted(false);
fullExitScreen.setContentAreaFilled(false); // 禁用背景填充(透明背景)
fullExitScreen.setFocusPainted(false); // 移除焦点框(可选)
openFileButton=new JButton("打开文件");
openFileButton.setFocusPainted(false);
JPanel jFilePanel =new JPanel();
jFilePanel.setOpaque(false);
jFilePanel.setBackground(null);
jFilePanel.setPreferredSize(new Dimension(100, 30));
openFileButton.setPreferredSize(new Dimension(90, 30));
jFilePanel.add(openFileButton);
// 获取类加载器
ClassLoader classLoader = VideoPlayer.class.getClassLoader();
playImage = new ImageIcon(classLoader.getResource("image/play.png"));
pauseImage = new ImageIcon(classLoader.getResource("image/pause.png"));
fullScreenImage = new ImageIcon(classLoader.getResource("image/full.png"));
exitScreenImage = new ImageIcon(classLoader.getResource("image/exitFull.png"));
fullExitScreen.setIcon(fullScreenImage);
playPauseButton.setIcon(pauseImage);
playPauseButton.addActionListener(e -> togglePlayback());
fullExitScreen.addActionListener(e -> toggleFullScreen());
openFileButton.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
openFile();
}
});
// canvasFrame.add(playPauseButton, BorderLayout.SOUTH);
startTimeJl = new JLabel("00:00");
durationJl = new JLabel();
// 创建进度条
progressBar = new JSlider(0, 100, 0); // 初始范围为 0-100%
progressBar.setMajorTickSpacing(10);
progressBar.setMinorTickSpacing(1);
progressBar.setSnapToTicks(true);
// 设置 JSlider 的首选大小
Dimension preferredSize = new Dimension(850, 20); // 宽度为300,高度为20
progressBar.setPreferredSize(preferredSize);
progressBar.setUI(new CustomSliderUI(progressBar));
progressBar.setFocusable(false); // 禁用焦点绘制
// progressBar.setPaintTicks(true);
//progressBar.setPaintLabels(true);
progressBar.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
userDragging=true;
//if (!progressBar.getValueIsAdjusting()) {
BasicSliderUI ui = (BasicSliderUI) progressBar.getUI();
int value = ui.valueForXPosition(e.getX());
progressBar.setValue(value);
// }
}
@Override
public void mouseReleased(MouseEvent e) {
userDragging = false;
}
});
// canvasFrame.setCanvasSize(800,500);
progressBar.addChangeListener(e -> {
try {
if (!isSelectFile) {
int position = progressBar.getValue();
//注释表示这个控制拖动中不生效,拖动完成才生效
//if(!progressBar.getValueIsAdjusting() || position==0 || position==100){
if (!isSelectFile && position==100) {
seekToPosition();
}
Thread.sleep(5);
seekToPosition();
// }
}
} catch (FFmpegFrameGrabber.Exception | InterruptedException ex) {
ex.printStackTrace();
return;
}
}); // 监听进度条变化
Canvas canvas=canvasFrame.getCanvas();
canvas.setBounds(0,0,1000,700);
JPanel jPanel =new JPanel();
JPanel jBarPanel =new JPanel();
jBarPanel.setOpaque(false);
jBarPanel.setBackground(null);
jBarPanel.setPreferredSize(new Dimension(1000, 25));
jPanel.setOpaque(false);
jPanel.setVisible(true);
jPanel.setBounds(0,700,1000,100);
jPanel.setOpaque(false);
jPanel.setVisible(true);
jPanel.setBackground(null);
jPanel.setLayout(new BorderLayout());
jBarPanel.add(startTimeJl);
jBarPanel.add(progressBar);
jBarPanel.add(durationJl);
jPanel.add(jBarPanel,BorderLayout.NORTH);
jPanel.add(playPauseButton,BorderLayout.CENTER);
jPanel.add(jFilePanel,BorderLayout.WEST);
jPanel.add(fullExitScreen,BorderLayout.EAST);
jPanel.setPreferredSize(new Dimension(1000, 100));
canvasFrame.add(jPanel,BorderLayout.SOUTH);
// 初始化 FFmpegFrameGrabber
try {
maxTimestampUs = getMaxTimestampUs(videoPath);
grabber = new FFmpegFrameGrabber(videoPath);
grabber.start();
setGrabberParam(grabber,videoPath.endsWith(".ts"));
// 获取视频的总时长(毫秒)
totalDuration = (long) grabber.getLengthInTime() / 1000;
durationJl.setText(convertMilliseconds(totalDuration));
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "打开文件时发生错误。");
return;
}
// 启动播放线程
startPlayback();
}
private void startPlayback() {
playbackThread = new Thread(() -> {
int audioStreamIndex = -1;
AudioPlayer audioPlayer=null;
// if (!grabber.getAudioCodecName().contains("mp3") && !grabber.getAudioCodecName().contains("aac")) {
// 获取音频流信息
audioPlayer = new AudioPlayer(grabber.getSampleRate(),16,grabber.getAudioChannels());
// }
if (audioStreamIndex == -1) {
System.err.println("No audio stream found.");
}
while (true) {
if (!isPlaying) {
// 暂停时,线程休眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
// 抓取下一帧
Frame frame = null;
// 定期更新进度条
if (!lock.isLocked()) {
try {
if (grabber.getTimestamp()<=0 && grabber.getVideoCodec()<=0) {
grabber.setTimestamp(0);
// grabber.start();
}
if (!isSelectFile && progressBar.getValue()==100) {
isPlaying=true;
//grabber.setTimestamp(grabber.getTimestamp());
}
frame = grabber.grabFrame();
} catch (IOException e) {
e.printStackTrace();
}
......................................
3.实现的打开文件选择功能。
private void openFile() {
if (!openFileButton.isEnabled()) {
return;
}
String uiClassName = UIManager.getLookAndFeel().getClass().getName();
setLookAndFeel(null);
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
List<String> allowList = new ArrayList<>(Arrays.asList("mp4", "mkv", "avi", "wmv","ts","flv","mp3", "wav", "ogg", "wma","aac","au","ac3","m4a"));
String[] allowArray = allowList.stream().toArray(String[]::new);
FileFilter fileFilter = new FileNameExtensionFilter("文件", allowArray);
fileChooser.setFileFilter(fileFilter);
fileChooser.addChoosableFileFilter(fileFilter);
int returnVal = fileChooser.showOpenDialog(null);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
try {
isPlaying=false;
isSelectFile=true;
grabber.setTimestamp(0);
grabber.stop();
grabber.close();
grabber.setAudioChannels(0);
grabber.setVideoCodec(0);
isSelectFile=true;
SwingUtilities.invokeLater(() -> {
int progress = progressBar.getValue();
progressBar.setValue(0);
//BoundedRangeModel model = progressBar.getModel();
// model.setValue(0); // 直接更新模型
if (progress==100) {
progressBar.setUI(new CustomSliderUI(progressBar));
progressBar.repaint();
}
lastProgressUpdate=0;
maxTimestampUs = getMaxTimestampUs(file.getAbsolutePath());
if (maxTimestampUs!=-2) {
grabber = new FFmpegFrameGrabber(file.getAbsolutePath());
try {
grabber.start();
} catch (FFmpegFrameGrabber.Exception e) {
e.printStackTrace();
}
this.videoPath=file.getAbsolutePath();
canvasFrame.setTitle("播放-"+file.getName());
setGrabberParam(grabber,file.getAbsolutePath().endsWith(".ts"));
// 获取视频的总时长(毫秒)
totalDuration = (long) grabber.getLengthInTime() / 1000;
startTimeJl.setText("00:00");
durationJl.setText(convertMilliseconds(totalDuration));
isSelectFile=false;
isPlaying=true;
Graphics g = canvasFrame.getCanvas().getGraphics();
g.clearRect(0, 0, canvasFrame.getCanvas().getWidth(), canvasFrame.getCanvas().getHeight());
playPauseButton.setIcon(pauseImage);
}
});
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "打开文件时发生错误。");
}
}
setLookAndFeel(uiClassName);
}
4.最后通过main方法启动。
public static void main(String[] args) throws UnsupportedEncodingException {
String videoPath = URLDecoder.decode(VideoPlayer.class.getResource("/video/赤伶.mp4").getPath(),"utf-8");
if (videoPath.startsWith("/")) {
videoPath = videoPath.substring(1);
}
// 创建并显示窗口
String finalVideoPath = videoPath;
SwingUtilities.invokeLater(() -> {
VideoPlayer player = new VideoPlayer(finalVideoPath,"播放-赤伶.mp4");
});
}
启动后效果如下:
放大后效果:
完整代码如下:
javacv实现音视频播放器源码
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)