zoukankan      html  css  js  c++  java
  • 基于QT前端的mplayer播放器2

    2、在PC端实现基于qt前端的mplayer播放器
    创建工程目录/home/linux/mplayer
    (1)搭建ui界面
    利用前面安装的designer搭建ui界面,并将其保存至/home/linux/mplayer/mplayer.ui
    #./designer
    圆角矩形标注: 加了一个widget,留作mplayer的播放区
    (2)编写程序
    在/home/linux/mymplayer/下创建mplayer.cpp、mplayer.h、main.cpp 、image.qrc
    Main.cpp
    /*****************************main.cpp*****************************/
    #include <QApplication>
    #include "mplayer.h"
    int main(int argc, char **argv)
    {
    QApplication app(argc, argv);
    MPlayer player;   //实例最终的MPlayer类
    player.show();     //显示界面
    return app.exec();  //运行程序
    }
    mplayer.h
    #ifndef _MPLAYER_H
    #define _MPLAYER_H
    #include <QIcon>
    #include <QProcess>
    #include <QTimer>
    #include <QStringList>
    #include <QDir>
    #include <QTime>
    #include <QString>
    #include "ui_mplayer.h"
    class MPlayer:public QDialog,private Ui_Dialog
    {
    Q_OBJECT
    public:
    MPlayer(QWidget *parent = 0);
    public:
    QTime int_to_time(int);
    public slots:             
    void play_pause_slots(); //暂停
    void stop_slots();      //停止
    void previous_slots();   //上一曲
    void next_slots();       //下一曲
    void seek_slots(int);
    void get_time_slots();        //得到播放时间
    void set_volume_slots(int);    //设置音量
    void set_sound_slots();       //静音
    void playerReward_slots();    //快退
    void playerForward_slots();    //快进
    void back_message_slots();    //更新显示信息
    private:
    QProcess *process;
    QStringList files;
    QDir directory;
    int file_count;
    QString file_name;
    bool isPlay;
    bool isSound;
    bool isStop;
    QTimer *timer;
    int file_length;
    int curr_time;
    };
    #endif
    mplayer.cpp
    /*******************************mplayer.cpp **********************************/
    #include "mplayer.h"
    #include <QDebug>
    #include <unistd.h>
    MPlayer::MPlayer(QWidget *parent)Dialog(parent)
    {
    setupUi(this);  //初始化界面
    isPlay = true;
    isSound = true;
    isStop = false;
    /************************为按键添加图标**************************/
    //play
    QIcon icon_play;
    icon_play.addPixmap(QPixmap(QString::fromUtf8("images/pause_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_2->setIcon(icon_play);
    //stop
    QIcon icon_stop;
    icon_stop.addPixmap(QPixmap(QString::fromUtf8("images/stop_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_3->setIcon(icon_stop);
    //reward
    QIcon icon_reward;
    icon_reward.addPixmap(QPixmap(QString::fromUtf8("images/reward_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_4->setIcon(icon_reward);
    //forward
    QIcon icon_forward;
    icon_forward.addPixmap(QPixmap(QString::fromUtf8("images/forward_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_5->setIcon(icon_forward);
    //sound
    QIcon icon_sound;
    icon_sound.addPixmap(QPixmap(QString::fromUtf8("images/sound_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton->setIcon(icon_sound);
    QIcon icon_previous;
    icon_previous.addPixmap(QPixmap(QString::fromUtf8("images/previous_disabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_6->setIcon(icon_previous);
    QIcon icon_next;
    icon_next.addPixmap(QPixmap(QString::fromUtf8("images/next_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_7->setIcon(icon_next);
    /************************设置按钮无边框**********************************/
    pushButton->setFlat(true);
    pushButton_2->setFlat(true);
    pushButton_3->setFlat(true);
    pushButton_4->setFlat(true);
    pushButton_5->setFlat(true);
    pushButton_6->setFlat(true);
    pushButton_7->setFlat(true);
    /*************************获得播放列表***************************/
    directory.setPath("./movie");
    files = directory.entryList(QDir::AllEntries,QDir::Time);
    file_name = files[2]; //文件0和1为 ”.” ”..”,所以从文件2开始播放
    file_count = 2;
    label_3->setText(files[2]);
    /*************************初始化进度条及QProcess类**************/
    horizontalSlider->setPageStep(1);
    process = new QProcess(this);
    process->setProcessChannelMode(QProcess::MergedChannels);
    /*************************初始化信号、槽*************************/
    connect(pushButton_2,SIGNAL(clicked()),this,SLOT(play_pause_slots()));
    connect(pushButton_3,SIGNAL(clicked()),this,SLOT(stop_slots()));
    connect(pushButton_4,SIGNAL(clicked()),this,SLOT(playerReward_slots()));
    connect(pushButton_5,SIGNAL(clicked()),this,SLOT(playerForward_slots()));
    connect(pushButton_6,SIGNAL(clicked()),this,SLOT(previous_slots()));
    connect(pushButton_7,SIGNAL(clicked()),this,SLOT(next_slots()));
    //connect(horizontalSlider,SIGNAL(valueChanged(int)),this,SLOT(seek_slots(int)));
    connect(spinBox,SIGNAL(valueChanged(int)),this,SLOT(set_volume_slots(int)));  
    connect(pushButton,SIGNAL(clicked()),this,SLOT(set_sound_slots()));
    connect(process,SIGNAL(readyReadStandardOutput()),this,SLOT(back_message_slots()));
    //当process可以读到Mplayer的返回信息时,产生readyReadStandardOutput()信号
    //process->start("mplayer -slave -quiet -ac mad 2.avi");
    //add -wid QWidget->winId();
    QString common = "mplayer -slave -quiet -ac mad -zoom movie/" + file_name + " -wid " + QString::number(widget->winId()); //这里的widget是ui中MPlayer的显示区
    process->start(common);      //开始运行程序
    spinBox->setValue(40);      
    timer = new QTimer(this);
    connect(timer,SIGNAL(timeout()),this,SLOT(get_time_slots()));
    //定时获取MPlayer的时间信息
    timer->start(1000); //启动定时器 1秒timeout 1次
    }
    void MPlayer::play_pause_slots()
    {
    if(!isPlay)
    {
    if(isStop)
    {
    file_name = files[file_count];
    QString common = "mplayer -slave -quiet -ac mad -zoom movie/" + file_name + " -wid " + QString::number(widget->winId());
    process->start(common);
    QIcon icon_stop;
    icon_stop.addPixmap(QPixmap(QString::fromUtf8("images/stop_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_3->setIcon(icon_stop);
    isStop = false;
    }
    else
    {
    process->write("pause\n");
    }
    QIcon icon_play;
    icon_play.addPixmap(QPixmap(QString::fromUtf8("images/pause_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_2->setIcon(icon_play);
    isPlay = true;
    }
    else
    {
    QIcon icon_pause;
    icon_pause.addPixmap(QPixmap(QString::fromUtf8("images/play_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_2->setIcon(icon_pause);
    isPlay = false;
    process->write("pause\n");
    }
    }
    void MPlayer::stop_slots()
    {
    if(!isStop)
    {
    process->write("quit\n");
    QIcon icon_pause;
    icon_pause.addPixmap(QPixmap(QString::fromUtf8("images/play_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_2->setIcon(icon_pause);
    isPlay = false;
    QIcon icon_stop;
    icon_stop.addPixmap(QPixmap(QString::fromUtf8("images/stop_disabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_3->setIcon(icon_stop);
    isStop = true;
    label->setText("00:00:00");
    label_2->setText("00:00:00");
    }
    }
    void MPlayer::previous_slots()
    {
    if(file_count > 2)
    {
    if(file_count == (files.size()-1))
    {
    QIcon icon_next;
    icon_next.addPixmap(QPixmap(QString::fromUtf8("images/next_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_7->setIcon(icon_next);
    }
    process->write("quit\n");
    process = new QProcess(this);
    connect(process,SIGNAL(readyReadStandardOutput()),this,SLOT(back_message_slots()));
    file_count--;
    if(!isStop)
    {
    file_name = files[file_count];
    QString common = "mplayer -slave -quiet -ac mad -zoom movie/" + file_name + " -wid " + QString::number(widget->winId());
    process->start(common);
    }
    if(file_count == 2)
    {
    QIcon icon_previous;
    icon_previous.addPixmap(QPixmap(QString::fromUtf8("images/previous_disabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_6->setIcon(icon_previous);
    }
    label_3->setText(files[file_count]);
    }
    }
    void MPlayer::next_slots()
    {
    if(file_count < (files.size()-1))
    {
    if(file_count == 2)
    {
    QIcon icon_previous;
    icon_previous.addPixmap(QPixmap(QString::fromUtf8("images/previous_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_6->setIcon(icon_previous);
    }
    process->write("quit\n");      
    process = new QProcess(this);
    connect(process,SIGNAL(readyReadStandardOutput()),this,SLOT(back_message_slots()));
    file_count++;
    if(!isStop)
    {
    file_name = files[file_count];
    QString common = "mplayer -slave -quiet -ac mad -zoom movie/" + file_name + " -wid " + QString::number(widget->winId());
    process->start(common);
    }
    if(file_count == (files.size()-1))
    {    
    QIcon icon_next;
    icon_next.addPixmap(QPixmap(QString::fromUtf8("images/next_disabled.png")), QIcon::Normal, QIcon::Off);
    pushButton_7->setIcon(icon_next);
    }
    }
    label_3->setText(files[file_count]);
    }
    void MPlayer::seek_slots(int seek_num)
    {
    qDebug()<<seek_num;
    if(process && process->state() == QProcess::Running )
    {
    process->write(QString("seek " + QString::number(qMin(seek_num,100)) + "1\n").toAscii());
    }
    }
    void MPlayer::get_time_slots()
    {
    if(isPlay)
    {
    process->write("get_time_pos\n");
    process->write("get_time_length\n");
    }
    }
    void MPlayer::set_volume_slots(int volume)
    {
    qDebug()<<volume;
    process->write(QString("volume +" + QString::number(volume) + " \n").toAscii());
    //process->write(QString("volume +1\n").toAscii());
    }
    void MPlayer::set_sound_slots()
    {
    if(isSound)
    {
    process->write("mute 1\n");
    QIcon icon_sound;
    icon_sound.addPixmap(QPixmap(QString::fromUtf8("images/nosound_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton->setIcon(icon_sound);
    isSound = false;
    }
    else
    {
    process->write("mute 0\n");
    QIcon icon_sound;
    icon_sound.addPixmap(QPixmap(QString::fromUtf8("images/sound_enabled.png")), QIcon::Normal, QIcon::Off);
    pushButton->setIcon(icon_sound);
    isSound = true;
    }
    }
    void MPlayer::playerReward_slots()
    {
    //bool ok;
    //int m=moviePosition.toInt(&ok);
    if (process && process->state()==QProcess::Running && !isPlay)
    {
    //QString cmd="seek "+QString::number(qMax(m-10,0))+" 1\n";
    //process->write(cmd.toAscii());
    qDebug()<<"Reward";
    }
    }
    void MPlayer::playerForward_slots()
    {
    //     groupBox->setVisible(false);
    //bool ok;
    //int m=moviePosition.toInt(&ok);
    /*    if (process && process->state()==QProcess::Running && !isPlay)
    {
    //QString cmd="seek "+QString::number(qMin(m+10,100))+" 1\n";
    //process->write(cmd.toAscii());
    qDebug()<<"Forward";
    }*/
    }
    void MPlayer::back_message_slots()
    {
    while(process->canReadLine())
    {
    QString message(process->readLine());
    QStringList message_list = message.split("=");
    if(message_list[0] == "ANS_TIME_POSITION")
    {
    curr_time = message_list[1].toDouble();//toInt();
    QTime time = int_to_time(curr_time);
    label->setText(time.toString("hh:mm:ss"));
    horizontalSlider->setValue(100 * curr_time / file_length);
    }
    else if(message_list[0] == "ANS_LENGTH")
    {
    file_length = message_list[1].toDouble();//toInt();
    QTime time = int_to_time(file_length);
    label_2->setText(time.toString("hh:mm:ss"));      
    }
    }
    }
    QTime MPlayer::int_to_time(int second)
    {
    int sec = 0, min = 0, hour = 0;
    QTime time;
    if(second < 60)     
    {
    sec = second;
    min = 0;
    hour = 0;
    }
    if(second >= 60 && second < 3600)
    {
    sec = second % 60;
    min = second / 60;
    hour = 0;
    }
    if(second >= 3600)
    {
    sec = second % 60;
    min = (second / 60) % 60;
    hour = second / 3600;
    }
    time.setHMS(hour,min,sec);
    return time;
    }
    image.qrc
    <RCC>
    <qresource prefix="images" >
    <file>images/player_play.png</file>
    <file>images/player_stop.png</file>
    <file>images/player_pause.png</file>
    <file>images/play_enabled.png</file>
    <file>images/pause_enabled.png</file>
    <file>images/reward_enabled.png</file>
    <file>images/forward_enabled.png</file>
    <file>images/stop_enabled.png</file>
    <file>images/sound_enabled.png</file>
    <file>images/nosound_enabled.png</file>
    <file>images/previous_enabled.png</file>
    <file>images/previous_disabled.png</file>
    <file>images/next_enabled.png</file>
    <file>images/next_disabled.png</file>
    </qresource>
    </RCC>
    (3)编译工程
    l         拷贝qmake到当前工程目录下
    #cp /home/linux/Qtopia/target/qtopiacore/host/bin/qmake ./
    l         生成项目文件、
    #qmake –project
    l         生成Makefile
    #qmake
    l         编译
    #make
    成功后,可以生成mplayer可执行程序
    l         建立movie和images
    #mkdir movie
    #mkdir images
    [root@localhost mplayer]# ls movie/
    1.mp3  2.avi  3.avi  4.avi  5.avi
    [root@localhost mplayer]# ls images
    forward_enabled.png  nosound_enabled.png  player_play.png        reward_enabled.png
    images               pause_enabled.png    player_stop.png        sound_enabled.png
    next_disabled.png    play_enabled.png     previous_disabled.png  stop_disabled.png
    next_enabled.png     player_pause.png     previous_enabled.png   stop_enabled.png
    l         运行程序
    [root@localhost mplayer]#./mplayer
    五、移植到ARM平台过程
    1、内核要求
    要求内核支持framebuffer驱动、OSS音频驱动、支持input事件的触摸屏驱动。
    2、目标板上部署qt环境
    将前面交叉编译好的/Qtopia目录拷贝到nfsroot目录下
    #cp /Qtopia /rootfs –a
    注:rootfs为目标平台的nfs根文件系统位置
    3、文件系统中移植tslib
    (下面的步骤是在ubantu-8.10环境下编译的,其它的系统基本相同)
    (1)拷贝“项目代码\tslib源码”目录下的tslib-1.4.tar.gz到linux系统
    (2)# tar -zxvf tslib-1.4.tar.gz
    # cd tslib-1.4
    # ./autogen.sh
    这一步需要安装一些工具,如:在ubantu系统下可以执行:sudo apt-get install automake
    (3)执行autogen.sh脚本所生成的Makefile文件
    请打入以下命令:
    echo "ac_cv_func_malloc_0_nonnull=yes" >$ARCH-linux.cache
    ./configure --host=arm-softfloat-linux-gnu  --prefix=/home/linux/tslib --cache-file=$ARCH-linux.cache
    --host是指你的交叉编译器的前最;例如:你的交叉编译器是arm-linux-gcc,则--host=arm-linux.如果是arm-softfloat-linux-gnu-gcc
    则--host=arm-softfloat-linux-gnu
    --prefix 是你执行make install 的时候编译后生成的可执行文件和库文件以及配置文所安装的目录;
    configure文件下还有好多选项,你可以执行./configure --help 来进行选择其他项,不过在这里这些选项就够了。
    (4)#make
    #make install
    (5)把指定安装目录下的mytslib的文件都copy到你所挂载的根文件下
    #cp -rf /home/linux/tslib/*    /rootfs/tslib
    (6)修改/rootfs/tslib下的etc目录中ts.cong文件
    #vi ts.conf  将第二行的#module_raw input修改成module_raw input 注意一定要顶格写否则程序执行时会发生读取ts.conf错误
    (7)启动你的开发板
    在终端上设置一下环境变量:
    export TSLIB_ROOT=/mytslib
    export TSLIB_TSDEVICE=/dev/event0
    export LD_LIBRARY_PATH=/mytslib/libLD_LIBRARY_PATH
    export QWS_SIZE=320x240
    export TSLIB_FBDEVICE=/dev/fb0
    export TSLIB_PLUGINDIR=/mytslib/lib/ts
    export TSLIB_CONSOLEDEVICE=none
    export TSLIB_CONFFILE=/mytslib/etc/ts.conf
    export POINTERCAL_FILE=/etc/pointercal
    export QWS_MOUSE_PROTO=Tslib:/dev/event0
    export TSLIB_CALIBFILE=/etc/pointercal
    export QWS_DISPLAY="LinuxFb:mmWidth100:mmHeight130:0"
    export TSLIB_TSEVENTTYYPE=H3600
    为了实现Tslib的正确运行,需要对如下的Tslib的环境变量进行配置:
    TSLIB_TSDEVICE  //触摸屏设备文件名。
    TSLIB_CALIBFILE  //校准的数据文件,由ts_calibrate校准程序生成。
    SLIB_CONFFILE  //配置文件名。
    TSLIB_PLUGINDIR //插件目录
    TSLIB_CONSOLEDEVICE //控制台设备文件名
    TSLIB_FBDEVICE  //设备名
    以上环境变量在实际开发中的实际配置可以根据实际情况决定。而这些指定的设备节点一定要和你的开发板上的/dev目录下的设备节点相对应。
    为了不浪费时间我们把上面的这些设置写入一个脚本里面:参见5.6节
    (8)就可以运行mytslib/bin下的测试文件,如ts_calibrate校准程序。
    4、文件系统中移植mplayer播放器
    需要在目标板上也移植开源的mplayer播放器,步骤如下:
    l         编译libmad
    重新配置前面针对主机编译过的libmad
    #./configure --enable-fpm=arm --host=arm-linux --disable-shared \
    --disable-debugging --prefix=/usr/local/arm/3.4.1/lib \
    CC=arm-linux-gcc
    #make
    就可以编译出libmad了。注意--prefix配置选项表示libmad库和头文件在哪个目录生成,比如本例中make install后在/usr/local/arm/3.4.1/lib目录下就多了include和lib两个目录。这与mplayer的配置选项--with-extraincdir指定的目录是相符的。如果没找到编译生成的lib库和include头文件。则在当前编译目录下的mad.h以及.libs目录下的libmad.a拷到你自己指定的目录下
    l         编译mplayer
    ./configure --cc=arm-linux-gcc --host-cc=gcc --target=arm-linux --enable-static \
    --prefix=/tmp/mplayer-rc2 --disable-win32dll --disable-dvdread \
    --enable-fbdev --disable-mencoder --disable-live --disable-mp3lib \
    --enable-mad --enable-libavcodec_a --language=zh_CN \
    --disable-armv5te --disable-armv6 \
    --with-extraincdir=/usr/local/arm/3.4.1/lib/include \
    --with-extralibdir=/usr/local/arm/3.4.1/lib/lib
    几点注意:—host-cc参数指定X86的gcc,不指定的话,有些必须用gcc编译的,make会交叉编译,就会出错
    --cc指定交叉工具链名称
    --with-extraincdir与--with-extralibdir指定刚才交叉编译libmad生成的mad.h与liblibmad.a存放路径
    编译过程中会出现如下错误:
    armv4l/dsputil_arm_s.S:79:error:selected processor does not support 'pld[r1]'
    【解决办法】
    修改dsputil_arm_s.S,在前面添加上:
    #ifndef HAVE_PLD
    .macro pld reg
    .endm
    #endif
    5、主机交叉编译工程
    (1)针对目标平台调整UI
    由于目标平台的液晶是320*240分辨率的,所以需要调整下UI的大小。将UI的整个窗口大小调整为320*240。
    (2)交叉编译工程
    l         拷贝针对目标平台的qmake工具到工程目录下
    #cp /home/linux/Qtopia/target/qtopiacore/target/bin/qmake ./
    l         生成项目文件
    #qmake –project
    l         生成Makefile文件
    #qmake
    l         编译生成目标文件mymplayer
    #make
    6、设置环境变量脚本文件Qtopia.sh
    #vim Qtopia.sh
    !/bin/sh
    export TSLIB_ROOT=/tslib
    export TSLIB_TSDEVICE=/dev/event0
    export LD_LIBRARY_PATH=/tslib/libLD_LIBRARY_PATH
    export QWS_SIZE=320x240
    export TSLIB_FBDEVICE=/dev/fb0
    export TSLIB_PLUGINDIR=/tslib/lib/ts
    export TSLIB_CONSOLEDEVICE=none
    export TSLIB_CONFFILE=/tslib/etc/ts.conf
    export POINTERCAL_FILE=/etc/pointercal
    export QWS_MOUSE_PROTO=Tslib:/dev/event0
    export TSLIB_CALIBFILE=/etc/pointercal
    export TSLIB_TSEVENTTYYPE=H3600i
    export LD_LIBRARY_PATH=/Qtopia/libLD_LIBRARY_PATH
    export QWS_SW_CURSOR
    export set HOME=/root
    export set QPEDIR=/Qtopia
    export set QWS_KEYBOARD="TTY:/dev/tty1"
    export QWS_DISPLAY="LinuxFb:mmWidth60:mmHeight65:0"    
    7、通过nfs方式测试程序
    (1)设置uboot参数
    setenv bootcmd tftp 30008000 zImage \; go zImage
    setenv bootargs root=nfs nfsroot=192.168.1.112:/rootfs ip=192.168.1.202 init=/linuxrc console=ttySAC0,115200 devfs=mount display=sam240
    (2)启动目标系统
    l         将“项目代码\工程镜像”目录下的zImage拷贝到tftp服务目录/tftpboot下
    l         开启nfs、tftp服务,服务目录分别为/rootfs /tftpboot
    l         启动系统
    l         运行测试程序
    #. ./Qtopia.sh  //设置环境变量
    #cd mymplayer
    #./mymplayer –qws  //运行程序
    8、QT程序国际化
    利用/home/linux/Qtopia/target/qtopiacore/host/bin下面的几个工具给程序做汉化工作
    (1)修改pro文件
    在.pro文件添加如下内容:
    TRANSLATIONS = zh_CN.ts
    (2)生成ts文件
    #lupdate mymplayer.pro
    生成zh_CN.ts文件
    (3)生成qm翻译文件
    #linguist zh_CN.ts
    (4)拷贝字体文件到目标系统
    l         从C:\WINDOWS\Fonts选择一个字体,如simsun.ttc
    cp simsun.ttc /rootfs/Qtopia/lib/fonts           //simsun.ttc 是宋体字库
    l         修改fonts中文件fontdir,添加如下内容
    simsun simsun.ttc TTC n 50 120 u     
    (5)修改源码
    修改main.cpp为如下形式
    #include <QApplication>
    #include "mplayer.h"
    #include <QtCore/QTextCodec>
    #include <QTranslator>
    int main(int argc, char **argv)
    {
    QApplication app(argc, argv);
    QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")) ;//设置编码为UTF8
    app.setFont(QFont("simsun", 10));                            //设置显示字体为汉字
    QTranslator *translator = new QTranslator( 0 );                              //导入中文化qm文件
    translator->load( "zh_CN.qm", "." );
    app.installTranslator( translator );
    MPlayer player;
    player.show();
    return app.exec();
    }
    9、目标机独立测试
    (1)裁剪系统
    (2)制作cramfs镜像

  • 相关阅读:
    MSMQ 跨服务器读写队列的“消息队列系统的访问被拒绝”的解决方案
    WCF中的ServiceHost初始化两种方式
    正则表达式规则
    常用正则表达式
    Visual Studio 2017使用Asp.Net Core构建Angular4应用程序
    斑马打印机ZT410中文打印
    CNPM 安装 for angularjs
    MAC OS X&Vmware
    HBase
    SQL 和 NoSQL 比较
  • 原文地址:https://www.cnblogs.com/zzxap/p/2175738.html
Copyright © 2011-2022 走看看