当前位置: 首页 > ai >正文

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)); // 设置边距为0playPauseButton.setBorderPainted(false);playPauseButton.setContentAreaFilled(false);    // 禁用背景填充(透明背景)playPauseButton.setFocusPainted(false);  // 移除焦点框(可选)playPauseButton.setMargin(new Insets(0, 0, 0, 0)); // 设置边距为0fullExitScreen=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() {@Overridepublic 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,高度为20progressBar.setPreferredSize(preferredSize);progressBar.setUI(new CustomSliderUI(progressBar));progressBar.setFocusable(false); // 禁用焦点绘制// progressBar.setPaintTicks(true);//progressBar.setPaintLabels(true);progressBar.addMouseListener(new MouseAdapter() {@Overridepublic void mousePressed(MouseEvent e) {userDragging=true;//if (!progressBar.getValueIsAdjusting()) {BasicSliderUI ui = (BasicSliderUI) progressBar.getUI();int value = ui.valueForXPosition(e.getX());progressBar.setValue(value);// }}@Overridepublic 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);// 初始化 FFmpegFrameGrabbertry {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实现音视频播放器源码

http://www.xdnf.cn/news/431.html

相关文章:

  • 字符串系列一>二进制求和
  • 【重走C++学习之路】12、模板进阶
  • 智慧农业新视界:视频监控管理平台如何赋能现代农业
  • Trae,字节跳动推出的 AI 编程助手插件
  • TensorFlow 实现 Mixture Density Network (MDN) 的完整说明
  • JavaScript 回调函数详解
  • spring三级缓存如何解决循环依赖问题
  • 数量关系 多级数列1
  • 文档内容提取以及合成
  • 卸载Anaconda并保留虚拟环境,重装Anaconda并还原之前的虚拟环境
  • [Swift]pod install成功后运行项目报错问题error: Sandbox: bash(84760) deny(1)
  • 老年保健与管理实训室建设要点:设备选型与技术应用关键
  • ELK日志系统
  • 卷积神经网络基础(二)
  • Redis-分布式锁
  • PyTorch深度学习框架60天进阶学习计划 - 第46天:自动化模型设计(二)
  • n8n 中文系列教程_02. 自动化平台深度解析:核心优势与场景适配指南
  • 【Linux】软件管理机制和软件安装
  • Python 赋能区块链教育:打造去中心化学习平台
  • 【专刷】滑动窗口(一)
  • CasualLanguage Model和Seq2Seq模型的区别
  • Day2—3:前端项目uniapp壁纸实战
  • MCP 协议——AI 世界的“USB-C 接口”:解锁智能协作的新时代
  • Linux(autoDL云服务器)mamba-ssm环境安装——一次成功!
  • [Java EE] Spring AOP 和 事务
  • 2025.04.19-阿里淘天春招算法岗笔试-第三题
  • C++——异常
  • 【正则表达式】正则表达式使用总结
  • QML动画--ParallelAnimation和SequentialAnimation
  • 《AI大模型应知应会100篇》第27篇:模型温度参数调节:控制创造性与确定性