用JAVA写一个视频播放器

前言

跳过废话,直接看正文

当年入坑java是因为它的跨平台优势。那时我认为,”编写一次,处处运行。”这听上去多么牛逼,应该是所有语言发展的终极之道,java势必会一统天下。

然而事实证明,那时的我还是太年轻。

正所谓鱼和熊掌不可兼得,若要享受跨平台带来的方便,便不可避免地要接受性能上的不足。事实上,java一直在致力于提高虚拟机的性能(JIT等技术),但面对对实时计算性能要求很高或涉及到用硬件优化的任务(视频的硬件编码、解码)时,仍远远比不上c或c++。因此,很少能够看到有人用java来写这类软件。

回到正题,要想写一个视频播放器,势必要处理不同的视频格式(mp4、avi、rmvb、flv等)。每一类格式的视频都必须要有对应的解码器才能播放。绝大多数解码器的库都是基于效率高的c或c++写成的,用java的写解码器的库非常少。

因此,要想用java写一个视频播放器,只有以下两个选择:

  • 使用纯java编写,自己编写主流视频格式的软件解码器、结合Java媒体框架(JMF)完成视频解码任务。
  • 使用jni调用现有的c或c++的解码器库,完成视频解码任务

使用java自己编写软件解码器的优点是能够真正地实现跨平台,也能很方便灵活地与swing框架结合。然而缺点是太过麻烦,先不说软件解码器的效率问题,各种主流视频格式的解码器的车轮能否成功地重造都是一个值得商榷的问题。

因此,我采用了第二种办法,通过jni调用现有的第三方视频解码库来完成视频解码的任务,而视频的显示、控制的逻辑则交由java来完成。这样便能够实现一个效率较高的视频播放器。值得一提的是,虽然根据平台类型来调用不同平台也能够实现跨平台,但是会受到诸多限制,这是不可避免的。

接下来便来看看具体怎么实现一个视频播放器吧。


正文

解码器的选择

视频格式有很多,解码器也有很多,因此要分别寻找对应的解码器并封装成jni接口进行调用是一个繁琐的任务,因此我们可以找一个已经封装了多种主流解码器的媒体处理框架(视频播放器)的库来直接进行调用。VLC是一个非常好的选择。

VLC media player VLC 是一款自由、开源的跨平台多媒体播放器及框架,可播放大多数多媒体文件,以及 DVD、音频 CD、VCD 及各类流媒体协议。

在vlc的库上,我们要用jni进行一定的封装,从而能用java来调用视频的解码和播放控制接口。好在我们不必重造车轮。vlcj是一位国外开发者用java开发的一个媒体处理框架,底层使用的正是vlc播放器,通过jni调用vlc来完成音频的解码任务。

开发流程

  • 安装VLC Media Player(我安装的版本是2.2.4,最好安装到默认目录)
  • 建立普通maven项目,添加vlcj的依赖
    <dependency>
        <groupId>uk.co.caprica</groupId>
        <artifactId>vlcj</artifactId>
        <version>3.10.1</version>
    </dependency>
  • 正式开发

接下来就是代码干货了,将会介绍三个项目,都是基于这个vlcj的视频播放器。


入门示例

这是vlcj作者提供的入门示例程序,能够快速实现一个简单的视频播放器,当然,仅仅是播放而已,不提供控制功能。
这里用的vlcj中提供的内嵌视频播放组件EmbeddedMediaPlayerComponent,通过其中的mediaPlayer中的playMedia方法可以直接播放视频,视频会渲染到EmbeddedMediaPlayerComponent上。EmbeddedMediaPlayerComponent这个组件中提供了很多其他功能,若要实现一个普通的视频播放器,了解这个组件便足够了。

代码
package tutorial;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import uk.co.caprica.vlcj.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.discovery.NativeDiscovery;

public class Tutorial {

    private final JFrame frame;

    private final EmbeddedMediaPlayerComponent mediaPlayerComponent;

    public static void main(final String[] args) {
        new NativeDiscovery().discover();
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Tutorial(args);
            }
        });
    }

    public Tutorial(String[] args) {
        frame = new JFrame("My First Media Player");
        frame.setBounds(100, 100, 600, 400);
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                mediaPlayerComponent.release();
                System.exit(0);
            }
        });
        mediaPlayerComponent = new EmbeddedMediaPlayerComponent();
        frame.setContentPane(mediaPlayerComponent);
        frame.setVisible(true);
        mediaPlayerComponent.getMediaPlayer().playMedia(args[0]);
    }
}

vlcj-player

这是vlcj作者自己基于vlcj写的一个开源播放器,功能很全面,完全可以用来作为平时的视频播放器来使用。由于这个项目内容较多,不便将代码列出。
项目地址详见https://github.com/caprica/vlcj-player

javaplayer

这个是我基于vlcj写的一个简单的视频播放器,支持一些简单的视频播放控制功能和播放弹幕的功能。
开发这个播放器是出于对java编写视频播放器的好奇心,因此没有提供比较完善的功能,毕竟耗时耗力。只是结合了我之前写过的一个弹幕播放器,为这个视频播放器添加了弹幕播放的功能,方便能在播放本地视频的过程中播放b站的弹幕。
项目地址详见https://github.com/clayandgithub/javaplayer

后记

在开发过程中遇到一些坑,如视频渲染组件的透明问题,以及swing的重绘机制导致的一些界面显示上的bug。有些完全解决了,有些则只是采用曲线救国的方法解决的。如果你在开也遇到了这些问题,欢迎一起交流探讨。

附:vlcj的官方文档

这是一个基于JavaFX的简单视频播放器的代码: ```java import javafx.application.Application; import javafx.beans.binding.Bindings; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Slider; import javafx.scene.layout.BorderPane; import javafx.scene.media.Media; import javafx.scene.media.MediaPlayer; import javafx.scene.media.MediaView; import javafx.stage.Stage; import javafx.util.Duration; import java.io.File; public class VideoPlayer extends Application { private final String MEDIA_URL = "file:///C:/video.mp4"; // 视频文件的路径 private final DoubleProperty rateProperty = new SimpleDoubleProperty(1.0); // 播放速度 private final DoubleProperty volumeProperty = new SimpleDoubleProperty(0.5); // 音量 @Override public void start(Stage primaryStage) throws Exception { // 加载视频文件 Media media = new Media(MEDIA_URL); MediaPlayer mediaPlayer = new MediaPlayer(media); mediaPlayer.setAutoPlay(true); // 创建MediaView用于显示视频 MediaView mediaView = new MediaView(mediaPlayer); // 创建Slider用于控制播放进度 Slider timeSlider = new Slider(); timeSlider.setMin(0); mediaPlayer.setOnReady(() -> { timeSlider.setMax(mediaPlayer.getTotalDuration().toSeconds()); }); mediaPlayer.currentTimeProperty().addListener((observable, oldValue, newValue) -> { timeSlider.setValue(newValue.toSeconds()); }); timeSlider.setOnMouseClicked(event -> { mediaPlayer.seek(Duration.seconds(timeSlider.getValue())); }); // 创建Slider用于控制音量 Slider volumeSlider = new Slider(); volumeSlider.setMin(0); volumeSlider.setMax(1); mediaPlayer.volumeProperty().bindBidirectional(volumeProperty); volumeSlider.valueProperty().bindBidirectional(volumeProperty); // 创建Button用于播放/暂停视频 Button playButton = new Button("播放"); playButton.setOnAction(event -> { if (mediaPlayer.getStatus() == MediaPlayer.Status.PLAYING) { mediaPlayer.pause(); playButton.setText("播放"); } else { mediaPlayer.play(); playButton.setText("暂停"); } }); // 创建Button用于停止视频 Button stopButton = new Button("停止"); stopButton.setOnAction(event -> { mediaPlayer.stop(); playButton.setText("播放"); }); // 创建Slider用于控制播放速度 Slider rateSlider = new Slider(0.5, 1.5, 1.0); mediaPlayer.rateProperty().bindBidirectional(rateProperty); rateSlider.valueProperty().bindBidirectional(rateProperty); // 创建布局 BorderPane pane = new BorderPane(); pane.setCenter(mediaView); pane.setBottom(timeSlider); BorderPane.setMargin(timeSlider, new javafx.geometry.Insets(10, 0, 0, 0)); pane.setLeft(volumeSlider); BorderPane.setMargin(volumeSlider, new javafx.geometry.Insets(0, 0, 0, 10)); pane.setTop(playButton); BorderPane.setAlignment(playButton, javafx.geometry.Pos.CENTER); pane.setRight(rateSlider); BorderPane.setMargin(rateSlider, new javafx.geometry.Insets(0, 10, 0, 0)); pane.setBottom(stopButton); BorderPane.setAlignment(stopButton, javafx.geometry.Pos.CENTER_RIGHT); BorderPane.setMargin(stopButton, new javafx.geometry.Insets(0, 0, 10, 10)); // 创建场景 Scene scene = new Scene(pane, 640, 480); // 设置舞台 primaryStage.setScene(scene); primaryStage.setTitle("视频播放器"); primaryStage.show(); } public static void main(String[] args) { launch(args); } } ``` 注意:这个代码需要将`MEDIA_URL`替换为你自己的视频文件路径。另外,需要在项目中添加JavaFX库的依赖,具体方法可以参考官方文档。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值