Update MediaClient

This commit is contained in:
2026-03-28 11:39:04 +11:00
parent 24dc6c7cd0
commit f3266566eb
1284 changed files with 462406 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#include "FileListModel.h"
#include "utils.h"
#include <QApplication>
#include <QBrush>
#include <QDir>
#include <QPalette>
FileListModel::FileListModel(int fileFormat, QObject *parent)
: QAbstractListModel(parent)
, m_bCheckable(false)
, m_fileFormat(fileFormat)
{
}
int FileListModel::rowCount(const QModelIndex & /* parent */) const
{
return m_fileCount;
}
QVariant FileListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= m_fileList.size() || index.row() < 0)
return QVariant();
if (role == Qt::DisplayRole)
{
return m_fileList.at(index.row());
}
else if (role == Qt::DecorationRole && m_fileFormat == FILE_FORMAT_PIC)
{
QString file = m_basePath + "/" + m_fileList.at(index.row());
QPixmap pixmap(file);
pixmap = pixmap.scaled(60, 40);
return pixmap;
}
else if (role == Qt::CheckStateRole && m_bCheckable)
{
return m_boolList.at(index.row()) ? Qt::Checked : Qt::Unchecked;
}
else if (role == Qt::SizeHintRole && m_fileFormat == FILE_FORMAT_VIDEO)
{
return QSize(100, 40);
}
return QVariant();
}
Qt::ItemFlags FileListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
if (index.row() >= m_fileList.size() || index.row() < 0)
return Qt::NoItemFlags;
if (m_bCheckable)
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
}
else
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
}
bool FileListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role != Qt::CheckStateRole)
return false;
if (index.row() >= m_fileList.size() || index.row() < 0)
return false;
m_boolList.replace(index.row(), value.toBool());
emit dataChanged(index, index);
return true;
}
bool FileListModel::canFetchMore(const QModelIndex & /* index */) const
{
if (m_fileCount < m_fileList.size())
return true;
else
return false;
}
void FileListModel::fetchMore(const QModelIndex & /* index */)
{
int remainder = m_fileList.size() - m_fileCount;
int itemsToFetch = qMin(100, remainder);
beginInsertRows(QModelIndex(), m_fileCount, m_fileCount+itemsToFetch-1);
m_fileCount += itemsToFetch;
endInsertRows();
}
void FileListModel::setNameFilters(const QStringList &filters)
{
m_nameFilters = filters;
}
bool FileListModel::removeRows(int pos, int count, const QModelIndex &parent)
{
if (pos >= m_fileList.size() || pos < 0)
return false;
beginRemoveRows(parent, pos, pos);
m_fileList.removeAt(pos);
m_boolList.removeAt(pos);
endRemoveRows();
return true;
}
void FileListModel::setDirPath(const QString &path)
{
QDir dir(path);
m_basePath = path;
dir.setNameFilters(m_nameFilters);
beginResetModel();
m_fileList = dir.entryList();
m_fileCount = 0;
for (int i = 0; i < m_fileList.size(); i++)
{
m_boolList.push_back(false);
}
endResetModel();
}
void FileListModel::setCheckable(bool checkable)
{
m_bCheckable = checkable;
if (m_fileCount > 0)
{
emit dataChanged(index(0), index(m_fileCount-1));
}
}

View File

@@ -0,0 +1,68 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#ifndef FILELISTMODEL_H
#define FILELISTMODEL_H
#include <QAbstractListModel>
#include <QList>
#include <QStringList>
#define FILE_FORMAT_PIC 0
#define FILE_FORMAT_VIDEO 1
typedef QList<bool> QBoolList;
class FileListModel : public QAbstractListModel
{
Q_OBJECT
public:
FileListModel(int fileFormat, QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
void setCheckable(bool checkable);
void setNameFilters(const QStringList &filters);
bool removeRows(int pos, int count, const QModelIndex &parent = QModelIndex());
public slots:
void setDirPath(const QString &path);
protected:
bool canFetchMore(const QModelIndex &parent) const;
void fetchMore(const QModelIndex &parent);
private:
QStringList m_nameFilters;
QStringList m_fileList;
QBoolList m_boolList;
int m_fileCount;
bool m_bCheckable;
QString m_basePath;
int m_fileFormat;
};
#endif // FILELISTMODEL_H

View File

@@ -0,0 +1,220 @@
#-------------------------------------------------
#
# Project created by QtCreator 2019-07-04T09:50:05
#
#-------------------------------------------------
QT += core gui widgets multimedia openglwidgets
TARGET = MediaClient
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += __STDC_CONSTANT_MACROS
DEFINES += HTTPS
DEFINES += REPLAY
DEFINES += OVER_HTTP
DEFINES += OVER_WEBSOCKET
ios {
DEFINES += IOS
}
android {
DEFINES += EPOLL
DEFINES += BACKCHANNEL
}
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
CONFIG += c++11
ios:CONFIG += sdk_no_version_check
INCLUDEPATH += ./formClass
INCLUDEPATH += ./media
INCLUDEPATH += ./ui
INCLUDEPATH += ../MediaClient/bm
INCLUDEPATH += ../MediaClient/http
INCLUDEPATH += ../MediaClient/librtmp
INCLUDEPATH += ../MediaClient/rtmp
INCLUDEPATH += ../MediaClient/rtp
INCLUDEPATH += ../MediaClient/rtsp
INCLUDEPATH += ../MediaClient/srt
INCLUDEPATH += ../MediaClient/ffmpeg/include
INCLUDEPATH += ../MediaClient/libsrt/include
INCLUDEPATH += ../MediaClient/openssl/include
SOURCES += \
formClass/About.cpp \
formClass/FileBrowse.cpp \
formClass/InstMsgDialog.cpp \
formClass/MediaClient.cpp \
formClass/OpenMedia.cpp \
formClass/SystemSetting.cpp \
formClass/VideoWidget.cpp \
media/audio_capture.cpp \
media/audio_decoder.cpp \
media/audio_encoder.cpp \
media/audio_play.cpp \
media/audio_play_qt.cpp \
media/avcodec_mutex.cpp \
media/avi_write.cpp \
media/file_player.cpp \
media/http_flv_player.cpp \
media/http_mjpeg_player.cpp \
media/media_codec.cpp \
media/media_parse.cpp \
media/media_util.cpp \
media/rtmp_player.cpp \
media/rtsp_player.cpp \
media/srt_player.cpp \
media/video_decoder.cpp \
media/video_player.cpp \
FileListModel.cpp \
main.cpp \
utils.cpp \
../MediaClient/bm/base64.cpp \
../MediaClient/bm/hqueue.cpp \
../MediaClient/bm/linked_list.cpp \
../MediaClient/bm/ppstack.cpp \
../MediaClient/bm/rfc_md5.cpp \
../MediaClient/bm/sha1.cpp \
../MediaClient/bm/sha256.cpp \
../MediaClient/bm/sys_buf.cpp \
../MediaClient/bm/sys_log.cpp \
../MediaClient/bm/sys_os.cpp \
../MediaClient/bm/util.cpp \
../MediaClient/bm/word_analyse.cpp \
../MediaClient/http/http_cln.cpp \
../MediaClient/http/http_flv_cln.cpp \
../MediaClient/http/http_mjpeg_cln.cpp \
../MediaClient/http/http_parse.cpp \
../MediaClient/http/http_test.cpp \
../MediaClient/librtmp/amf.c \
../MediaClient/librtmp/hashswf.c \
../MediaClient/librtmp/log.c \
../MediaClient/librtmp/parseurl.c \
../MediaClient/librtmp/rtmp.c \
../MediaClient/rtmp/rtmp_cln.cpp \
../MediaClient/rtp/aac_rtp_rx.cpp \
../MediaClient/rtp/h264_rtp_rx.cpp \
../MediaClient/rtp/h264_util.cpp \
../MediaClient/rtp/h265_rtp_rx.cpp \
../MediaClient/rtp/h265_util.cpp \
../MediaClient/rtp/mjpeg_rtp_rx.cpp \
../MediaClient/rtp/mjpeg_tables.cpp \
../MediaClient/rtp/mpeg4.cpp \
../MediaClient/rtp/mpeg4_rtp_rx.cpp \
../MediaClient/rtp/pcm_rtp_rx.cpp \
../MediaClient/rtp/rtp.cpp \
../MediaClient/rtp/rtp_rx.cpp \
../MediaClient/rtp/ts_parser.cpp \
../MediaClient/rtsp/rtsp_backchannel.cpp \
../MediaClient/rtsp/rtsp_cln.cpp \
../MediaClient/rtsp/rtsp_parse.cpp \
../MediaClient/rtsp/rtsp_rcua.cpp \
../MediaClient/rtsp/rtsp_util.cpp \
../MediaClient/rtsp/rtsp_ws.cpp \
../MediaClient/srt/srt_cln.cpp
ios {
SOURCES += \
ios/file_view_controller.mm \
ios/ios_launcher.mm \
}
android {
SOURCES += \
media/audio_capture_android.cpp \
media/gles_engine.cpp \
media/gles_input.cpp \
}
HEADERS += \
formClass/About.h \
formClass/FileBrowse.h \
formClass/InstMsgDialog.h \
formClass/MediaClient.h \
formClass/OpenMedia.h \
formClass/SystemSetting.h \
formClass/VideoWidget.h \
media/audio_play_qt.h \
media/file_player.h \
media/http_flv_player.h \
media/http_mjpeg_player.h \
media/rtmp_player.h \
media/rtsp_player.h \
media/srt_player.h \
media/video_player.h \
FileListModel.h \
FORMS += \
ui/About.ui \
ui/FileBrowse.ui \
ui/InstMsgDialog.ui \
ui/MediaClient.ui \
ui/OpenMedia.ui \
ui/SystemSetting.ui
ios {
LIBS += -L$$PWD/lib/ios-armv8a
}
android {
LIBS += -L$$PWD/lib/$$ANDROID_TARGET_ARCH
ANDROID_EXTRA_LIBS += $$PWD/lib/$$ANDROID_TARGET_ARCH/libcrypto.so
ANDROID_EXTRA_LIBS += $$PWD/lib/$$ANDROID_TARGET_ARCH/libssl.so
}
LIBS += -lavformat
LIBS += -lswscale
LIBS += -lavcodec
LIBS += -lswresample
LIBS += -lavutil
LIBS += -lx264
LIBS += -lx265
LIBS += -lsrt
LIBS += -lcrypto
LIBS += -lssl
ios {
LIBS += -lbz2
LIBS += -liconv
LIBS += -framework AudioToolbox
LIBS += -framework VideoToolbox
}
android {
LIBS += -lopus
LIBS += -lOpenSLES
}
RESOURCES += MediaClient.qrc
ios {
QMAKE_INFO_PLIST = ios/Info.plist
}
android {
DISTFILES += \
android/AndroidManifest.xml \
android/build.gradle \
android/gradle.properties \
android/gradle/wrapper/gradle-wrapper.jar \
android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew \
android/gradlew.bat \
android/res/values/libs.xml \
android/res/xml/filepaths.xml
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
}

View File

@@ -0,0 +1,21 @@
<RCC>
<qresource prefix="/res">
<file>Resources/btn_pause.png</file>
<file>Resources/btn_play.png</file>
<file>Resources/btn_stop.png</file>
<file>Resources/mic.png</file>
<file>Resources/mute.png</file>
<file>Resources/snapshot.png</file>
<file>Resources/stop_record.png</file>
<file>Resources/video_record.png</file>
<file>Resources/volume.png</file>
<file>Resources/stopmic.png</file>
<file>Resources/1.png</file>
<file>Resources/4.png</file>
<file>Resources/2.png</file>
<file>Resources/back.png</file>
<file>Resources/delete.png</file>
<file>Resources/6.png</file>
<file>Resources/9.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.happytimesoft.mediaclient" android:installLocation="auto" android:versionCode="22" android:versionName="3.2">
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true"/>
<application android:name="org.qtproject.qt.android.bindings.QtApplication" android:hardwareAccelerated="true" android:requestLegacyExternalStorage="true" android:allowNativeHeapPointerTagging="false" android:label="@string/app_name" android:allowBackup="false" android:fullBackupOnly="false" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:launchMode="singleTop" android:screenOrientation="unspecified" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.arguments" android:value="-- %%INSERT_APP_ARGUMENTS%% --"/>
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
</activity>
<provider android:name="androidx.core.content.FileProvider" android:authorities="org.happytimesoft.mediaclient.fileprovider" android:grantUriPermissions="true" android:exported="false">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/>
</provider>
</application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.DEVICE_POWER"/>
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,87 @@
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
}
}
repositories {
google()
jcenter()
mavenCentral()
}
apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation "androidx.security:security-crypto:1.1.0-alpha03"
implementation "androidx.security:security-identity-credential:1.0.0-alpha02"
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qtAndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion
buildToolsVersion androidBuildToolsVersion
ndkVersion androidNdkVersion
// Extract native libraries from the APK
packagingOptions.jniLibs.useLegacyPackaging true
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qtAndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
tasks.withType(JavaCompile) {
options.incremental = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion 24
targetSdkVersion qtTargetSdkVersion
ndk.abiFilters = qtTargetAbiList.split(",")
}
}

View File

@@ -0,0 +1,16 @@
# Project-wide Gradle settings.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2500m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# Enable building projects in parallel
org.gradle.parallel=true
# Gradle caching allows reusing the build artifacts from a previous
# build with the same inputs. However, over time, the cache size will
# grow. Uncomment the following line to enable it.
#org.gradle.caching=true
android.useAndroidX=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,21 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<!-- DO NOT EDIT THIS: This file is populated automatically by the deployment tool. -->
<array name="bundled_libs">
<!-- %%INSERT_EXTRA_LIBS%% -->
</array>
<array name="qt_libs">
<!-- %%INSERT_QT_LIBS%% -->
</array>
<array name="load_local_libs">
<!-- %%INSERT_LOCAL_LIBS%% -->
</array>
<string name="static_init_classes"><!-- %%INSERT_INIT_CLASSES%% --></string>
<string name="use_local_qt_libs"><!-- %%USE_LOCAL_QT_LIBS%% --></string>
<string name="bundle_local_qt_libs"><!-- %%BUNDLE_LOCAL_QT_LIBS%% --></string>
<string name="system_libs_prefix"><!-- %%SYSTEM_LIBS_PREFIX%% --></string>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path path="." name="external" />
<files-path path="." name="path" />
</paths>

View File

@@ -0,0 +1,142 @@
package org.happytimesoft.util;
import java.io.File;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Build;
import android.os.StrictMode;
import androidx.core.content.FileProvider;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.content.ActivityNotFoundException;
public class HtUtil
{
private static MulticastLock m_wifiLock;
private static WakeLock m_wakeLock;
public HtUtil()
{
}
public static void enableMulticast(Context context)
{
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
m_wifiLock = wm.createMulticastLock("htutil");
if (m_wifiLock != null)
{
m_wifiLock.setReferenceCounted(true);
m_wifiLock.acquire();
}
}
public static void disableMulticast()
{
if (m_wifiLock != null)
{
m_wifiLock.release();
m_wifiLock = null;
}
}
private static String getMIMEType(String filename)
{
if (filename.endsWith(".jpg") ||
filename.endsWith(".jpeg") ||
filename.endsWith(".JPG") ||
filename.endsWith(".JPEG"))
return "image/jpeg";
if (filename.endsWith(".mp4") || filename.endsWith(".MP4"))
return "video/mp4";
if (filename.endsWith(".avi") || filename.endsWith(".AVI"))
return "video/x-msvideo";
if (filename.endsWith(".txt") || filename.endsWith(".TXT"))
return "text/plain";
else
return "*/*";
}
public static void openFile(Context context, String path, String provider)
{
Uri uri;
Intent intent = new Intent(Intent.ACTION_VIEW);
File file = new File(path);
String type = getMIMEType(path);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
{
uri = FileProvider.getUriForFile(context, provider, file);
}
else
{
uri = Uri.fromFile(file);
}
try {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(uri, type);
intent.addCategory(Intent.CATEGORY_DEFAULT);
context.startActivity(intent);
}
catch (ActivityNotFoundException e)
{
}
}
public static void disableLockScreen(Context context)
{
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
m_wakeLock = pm.newWakeLock(pm.SCREEN_BRIGHT_WAKE_LOCK | pm.ON_AFTER_RELEASE, "htutil");
if (m_wakeLock != null)
{
m_wakeLock.acquire();
}
}
public static void enableLockScreen()
{
if (null != m_wakeLock && m_wakeLock.isHeld())
{
m_wakeLock.release();
m_wakeLock = null;
}
}
public static int requestPermission(Context context, String permission)
{
int r = ContextCompat.checkSelfPermission(context, permission);
if (r != PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions((Activity)context, new String[]{permission}, 10000);
r = ContextCompat.checkSelfPermission(context, permission);
if (r != PackageManager.PERMISSION_GRANTED)
{
return 0;
}
else
{
return 1;
}
}
else
{
return 1;
}
}
}

View File

@@ -0,0 +1,63 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#ifndef CONFIG_H
#define CONFIG_H
#include "sys_inc.h"
#include <QString>
#define VERSION_STRING "V3.2"
#define LANG_SYS 0 // system language
#define LANG_ENG 1 // english
#define LANG_ZH 2 // chinese
#define FILE_PROVIDER "org.happytimesoft.mediaclient.fileprovider"
typedef struct
{
BOOL enableLog; // enable log
BOOL rtpMulticast; // whether enable rtp multicast via rtsp
BOOL rtpOverUdp; // prefer to use RTP OVER UDP
BOOL rtspOverHttp; // rtsp over http
BOOL rtspOverWs; // rtsp over websocket
int videoRenderMode; // 0 - Keep the original aspect ratio, 1 - Filling the whole window
int logLevel; // log level, reference sys_log.h
int rtspOverHttpPort; // rtsp over http port
int rtspOverWsPort; // rtsp over websocket port
int sysLang; // system language
int recordSize; // max video recording size, kb
int recordTime; // max video recording time, second
int hwDecoding; // hardware-acceleration decoding
int layoutMode; // layout mode
} SysConfig;
typedef struct
{
QString url; // url
QString user; // login user
QString pass; // login pass
} CHANNEL;
#endif

View File

@@ -0,0 +1,43 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#include "About.h"
#include "config.h"
About::About(QWidget *parent)
: QDialog(parent)
{
ui.setupUi(this);
ui.labVersion->setText(QString("Happytime Media Client %1").arg(VERSION_STRING));
connect(ui.btnOK, SIGNAL(clicked()), this, SLOT(close()));
showMaximized();
}
About::~About()
{
}

View File

@@ -0,0 +1,41 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#ifndef _ABOUT_H_
#define _ABOUT_H_
#include <QDialog>
#include "ui_About.h"
class About : public QDialog
{
Q_OBJECT
public:
About(QWidget *parent = 0);
~About();
private:
Ui::About ui;
};
#endif

View File

@@ -0,0 +1,186 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#include "FileBrowse.h"
#include "ui_FileBrowse.h"
#include "utils.h"
#include "config.h"
#include <QUrl>
#include <QDesktopServices>
#include <QMessageBox>
#include <QFile>
#include <QDir>
#include <QProcess>
#include <QScreen>
#include <QApplication>
#if defined(ANDROID)
#include <QJniObject>
#elif defined(IOS)
#include <ios/ios_launcher.h>
#endif
FileBrowse::FileBrowse(int fileFormat, QWidget *parent)
: QDialog(parent)
, ui(new Ui::FileBrowse)
, m_bEditing(false)
, m_fileFormat(fileFormat)
{
ui->setupUi(this);
initDialog();
connSignalSlot();
}
FileBrowse::~FileBrowse()
{
delete ui;
}
void FileBrowse::initDialog()
{
QStringList filters;
if (m_fileFormat == FILE_FORMAT_PIC)
{
filters << "*.jpg";
m_basePath = getSnapshotPath();
ui->labTips->setText(tr("Album"));
}
else
{
filters << "*.mp4";
filters << "*.avi";
m_basePath = getRecordPath();
ui->labTips->setText(tr("Video"));
}
ui->labPath->setText(tr("Path") + QString(" : ") + m_basePath);
m_pModel = new FileListModel(m_fileFormat, this);
m_pModel->setNameFilters(filters);
m_pModel->setDirPath(m_basePath);
ui->chkAll->setVisible(false);
ui->fileList->setModel(m_pModel);
showMaximized();
}
void FileBrowse::connSignalSlot()
{
QObject::connect(ui->btnBack, SIGNAL(clicked()), this, SLOT(slotBack()));
QObject::connect(ui->btnEdit, SIGNAL(clicked()), this, SLOT(slotEdit()));
QObject::connect(ui->chkAll, SIGNAL(clicked(bool)), this, SLOT(slotCheckAll(bool)));
QObject::connect(ui->fileList, SIGNAL(clicked(QModelIndex)), this, SLOT(slotItemClicked(QModelIndex)));
}
void FileBrowse::slotBack()
{
close();
}
void FileBrowse::slotEdit()
{
if (!m_bEditing)
{
m_bEditing = true;
ui->chkAll->setVisible(true);
m_pModel->setCheckable(true);
}
else
{
deleteSelectedFiles();
m_bEditing = false;
ui->chkAll->setVisible(false);
m_pModel->setCheckable(false);
}
}
void FileBrowse::slotCheckAll(bool flag)
{
int row = m_pModel->rowCount();
QModelIndex index;
for (int i = 0; i < row; i++)
{
index = m_pModel->index(i);
m_pModel->setData(index, flag, Qt::CheckStateRole);
}
}
void FileBrowse::slotItemClicked(QModelIndex index)
{
if (m_bEditing)
{
int flag = m_pModel->data(index, Qt::CheckStateRole).toInt();
if (flag == Qt::Checked)
{
m_pModel->setData(index, false, Qt::CheckStateRole);
}
else
{
m_pModel->setData(index, true, Qt::CheckStateRole);
}
}
else
{
QString name = m_pModel->data(index, Qt::DisplayRole).toString();
QString path = m_basePath + "/" + name;
#if defined(ANDROID)
QJniObject str = QJniObject::fromString(path);
QJniObject provider = QJniObject::fromString(FILE_PROVIDER);
QJniObject::callStaticMethod<void>("org/happytimesoft/util/HtUtil",
"openFile",
"(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V",
QNativeInterface::QAndroidApplication::context(),
str.object<jstring>(),
provider.object<jstring>());
#elif defined(IOS)
iosLaunchFile(path);
#endif
}
}
void FileBrowse::deleteSelectedFiles()
{
int flag;
int row = m_pModel->rowCount();
QModelIndex index;
for (int i = row - 1; i >= 0; i--)
{
index = m_pModel->index(i);
flag = m_pModel->data(index, Qt::CheckStateRole).toInt();
if (flag == Qt::Checked)
{
QString name = m_pModel->data(index, Qt::DisplayRole).toString();
QString path = m_basePath + "/" + name;
QFile::remove(path);
m_pModel->removeRows(i, 1, index.parent());
}
}
}

View File

@@ -0,0 +1,60 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#ifndef FILEBROWSE_H
#define FILEBROWSE_H
#include <QDialog>
#include "FileListModel.h"
namespace Ui {
class FileBrowse;
}
class FileBrowse : public QDialog
{
Q_OBJECT
public:
explicit FileBrowse(int fileFormat, QWidget *parent = 0);
~FileBrowse();
private slots:
void slotBack();
void slotEdit();
void slotCheckAll(bool flag);
void slotItemClicked(QModelIndex);
private:
void initDialog();
void connSignalSlot();
void deleteSelectedFiles();
private:
Ui::FileBrowse *ui;
QString m_basePath;
bool m_bEditing;
FileListModel * m_pModel;
int m_fileFormat;
};
#endif // FILEBROWSE_H

View File

@@ -0,0 +1,50 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#include "InstMsgDialog.h"
#include "ui_InstMsgDialog.h"
#include <QTimer>
#include <QGuiApplication>
#include <QScreen>
#include "utils.h"
InstMsgDialog::InstMsgDialog(QString msg, int timelen, QWidget *parent)
: QDialog(parent)
, ui(new Ui::InstMsgDialog)
{
ui->setupUi(this);
QRect rect = this->rect();
rect.setWidth(QGuiApplication::primaryScreen()->geometry().width());
rect.setHeight(40);
setGeometry(rect);
ui->labMsg->setText(msg);
QTimer::singleShot(timelen, this, SLOT(close()));
}
InstMsgDialog::~InstMsgDialog()
{
delete ui;
}

View File

@@ -0,0 +1,43 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#ifndef INSTMSGDIALOG_H
#define INSTMSGDIALOG_H
#include <QDialog>
namespace Ui {
class InstMsgDialog;
}
class InstMsgDialog : public QDialog
{
Q_OBJECT
public:
explicit InstMsgDialog(QString msg, int timelen, QWidget *parent = 0);
~InstMsgDialog();
private:
Ui::InstMsgDialog *ui;
};
#endif // INSTMSGDIALOG_H

View File

@@ -0,0 +1,863 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#include "MediaClient.h"
#include "InstMsgDialog.h"
#include "OpenMedia.h"
#include "config.h"
#include "FileBrowse.h"
#include "About.h"
#include "utils.h"
#include "SystemSetting.h"
#include "rtsp_cln.h"
#include "rtmp_cln.h"
#include "http_flv_cln.h"
#include "http_mjpeg_cln.h"
#include "srt_cln.h"
#include <QScreen>
#include <QTransform>
#include <QMessageBox>
#include <QDesktopServices>
#include <QStandardPaths>
#if defined(ANDROID)
#include <QJniObject>
#endif
MediaClient::MediaClient(QWidget *parent, Qt::WindowFlags flags)
: QDialog(parent, flags)
, m_curWidget(NULL)
, m_recvBytes(0)
{
ui.setupUi(this);
initDialog();
connSignalSlot();
}
MediaClient::~MediaClient()
{
QList<CHANNEL> channels;
saveChannel(channels, ui.videoWidget1);
saveChannel(channels, ui.videoWidget2);
saveChannel(channels, ui.videoWidget3);
saveChannel(channels, ui.videoWidget4);
saveChannel(channels, ui.videoWidget5);
saveChannel(channels, ui.videoWidget6);
saveChannel(channels, ui.videoWidget7);
saveChannel(channels, ui.videoWidget8);
saveChannel(channels, ui.videoWidget9);
saveChannels(channels);
saveLayoutMode(m_layoutMode);
ui.videoWidget1->stop();
ui.videoWidget2->stop();
ui.videoWidget3->stop();
ui.videoWidget4->stop();
ui.videoWidget5->stop();
ui.videoWidget6->stop();
ui.videoWidget7->stop();
ui.videoWidget8->stop();
ui.videoWidget9->stop();
#if defined(ANDROID)
QJniObject::callStaticMethod<void>("org/happytimesoft/util/HtUtil", "enableLockScreen");
#endif
log_close();
QDesktopServices::openUrl(QUrl("https://www.happytimesoft.com/", QUrl::TolerantMode));
}
void MediaClient::saveChannel(QList<CHANNEL> &channels, VideoWidget * widget)
{
CHANNEL channel;
if (widget->isPlaying())
{
channel.url = widget->getUrl();
channel.user = widget->getUser();
channel.pass = widget->getPass();
}
else
{
channel.url = "";
channel.user = "";
channel.pass = "";
}
channels.append(channel);
}
void MediaClient::closeEvent(QCloseEvent * event)
{
if (QMessageBox::Yes == QMessageBox::question(this, tr("Quit"), tr("Are you sure want to quit?")))
{
event->accept();
}
else
{
event->ignore();
}
}
void MediaClient::initLog()
{
char file[512] = {'\0'};
QString path = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).last();
sprintf(file, "%s/mediaclient.log", path.toStdString().c_str());
log_init(file);
}
void MediaClient::initDialog()
{
setWindowTitle(QString("Happytime media client %1").arg(VERSION_STRING));
#if defined(ANDROID)
QJniObject str = QJniObject::fromString("android.permission.WRITE_EXTERNAL_STORAGE");
QJniObject::callStaticMethod<jint>("org/happytimesoft/util/HtUtil",
"requestPermission",
"(Landroid/content/Context;Ljava/lang/String;)I",
QNativeInterface::QAndroidApplication::context(),
str.object<jstring>());
#endif
loadSystemConfig();
if (m_syscfg.enableLog)
{
initLog();
log_set_level(m_syscfg.logLevel);
}
ui.labTipInfo->setText("");
ui.labStatistics->setText("");
ui.cmbLayout->addItem(QIcon(":/res/Resources/1.png"), "1", 1);
ui.cmbLayout->addItem(QIcon(":/res/Resources/2.png"), "2", 2);
ui.cmbLayout->addItem(QIcon(":/res/Resources/4.png"), "4", 4);
ui.cmbLayout->addItem(QIcon(":/res/Resources/6.png"), "6", 6);
ui.cmbLayout->addItem(QIcon(":/res/Resources/9.png"), "9", 9);
QScreen * pScreen = QApplication::primaryScreen();
setupLayout(pScreen->orientation());
connect(pScreen, SIGNAL(orientationChanged(Qt::ScreenOrientation)),
this, SLOT(slotOrientationChanged(Qt::ScreenOrientation)));
if (1 == m_syscfg.layoutMode)
{
slotLayoutOne();
ui.cmbLayout->setCurrentIndex(0);
}
else if (2 == m_syscfg.layoutMode)
{
slotLayoutTwo();
ui.cmbLayout->setCurrentIndex(1);
}
else if (6 == m_syscfg.layoutMode)
{
slotLayoutSix();
ui.cmbLayout->setCurrentIndex(3);
}
else if (9 == m_syscfg.layoutMode)
{
slotLayoutNine();
ui.cmbLayout->setCurrentIndex(4);
}
else
{
slotLayoutFour();
ui.cmbLayout->setCurrentIndex(2);
}
m_curWidget = ui.videoWidget1;
m_curWidget->parentWidget()->setStyleSheet("background-color: blue;");
// when video playing, prevent auto lock screen
#if defined(ANDROID)
QJniObject::callStaticMethod<void>("org/happytimesoft/util/HtUtil",
"disableLockScreen",
"(Landroid/content/Context;)V",
QNativeInterface::QAndroidApplication::context());
#endif
loadChannels();
#ifndef IOS
ui.btnQuit->hide();
#endif
}
void MediaClient::connSignalSlot()
{
connect(ui.cmbLayout, SIGNAL(currentIndexChanged(int)), this, SLOT(slotLayoutChanged(int)));
connect(ui.btnPlay, SIGNAL(clicked()), this, SLOT(slotPlay()));
connect(ui.btnPause, SIGNAL(clicked()), this, SLOT(slotPause()));
connect(ui.btnStop, SIGNAL(clicked()), this, SLOT(slotStop()));
connect(ui.btnSnapshot, SIGNAL(clicked()), this, SLOT(slotSnapshot()));
connect(ui.btnRecord, SIGNAL(clicked()), this, SLOT(slotRecord()));
connect(ui.btnVolume, SIGNAL(clicked()), this, SLOT(slotVolume()));
connect(ui.btnSetting, SIGNAL(clicked()), this, SLOT(slotSystemSetting()));
connect(ui.btnOpenSnapshot, SIGNAL(clicked()), this, SLOT(slotOpenSnapshot()));
connect(ui.btnOpenRecord, SIGNAL(clicked()), this, SLOT(slotOpenRecording()));
connect(ui.btnAbout, SIGNAL(clicked()), this, SLOT(slotAbout()));
connect(ui.btnQuit, SIGNAL(clicked()), this, SLOT(close()));
connVideoWidget(ui.videoWidget1);
connVideoWidget(ui.videoWidget2);
connVideoWidget(ui.videoWidget3);
connVideoWidget(ui.videoWidget4);
connVideoWidget(ui.videoWidget5);
connVideoWidget(ui.videoWidget6);
connVideoWidget(ui.videoWidget7);
connVideoWidget(ui.videoWidget8);
connVideoWidget(ui.videoWidget9);
}
void MediaClient::connVideoWidget(VideoWidget * widget)
{
connect(widget, SIGNAL(snapshotResult(bool)), this, SLOT(slotSnapshotResult(bool)));
connect(widget, SIGNAL(recordResult(bool)), this, SLOT(slotRecordResult(bool)));
connect(widget, SIGNAL(updateStatistics(int)), this, SLOT(slotUpdateStatistics(int)));
connect(widget, SIGNAL(widgetSelecting(QWidget*)), this, SLOT(slotWidgetSelecting(QWidget*)));
connect(widget, SIGNAL(callState(QWidget*,int)), this, SLOT(slotCallState(QWidget*,int)));
}
void MediaClient::loadSystemConfig()
{
m_syscfg.enableLog = getEnableLogFlag();
m_syscfg.videoRenderMode = getVideoRenderMode();
m_syscfg.rtpMulticast = getRtpMulticast();
m_syscfg.rtpOverUdp = getRtpOverUdp();
m_syscfg.rtspOverHttp = getRtspOverHttp();
m_syscfg.rtspOverHttpPort = getRtspOverHttpPort();
m_syscfg.rtspOverWs = getRtspOverWs();
m_syscfg.rtspOverWsPort = getRtspOverWsPort();
m_syscfg.logLevel = getLogLevel();
m_syscfg.hwDecoding = getHWDecoding();
m_syscfg.layoutMode = getLayoutMode();
m_syscfg.recordTime = getRecordTime();
m_syscfg.recordSize = getRecordSize();
}
void MediaClient::loadChannels()
{
QList<CHANNEL> channels;
getChannels(channels);
if (channels.size() > 0 && !channels[0].url.isEmpty())
{
ui.videoWidget1->play(channels[0].url, channels[0].user, channels[0].pass);
}
if (channels.size() > 1 && !channels[1].url.isEmpty())
{
ui.videoWidget2->play(channels[1].url, channels[1].user, channels[1].pass);
}
if (channels.size() > 2 && !channels[2].url.isEmpty())
{
ui.videoWidget3->play(channels[2].url, channels[2].user, channels[2].pass);
}
if (channels.size() > 3 && !channels[3].url.isEmpty())
{
ui.videoWidget4->play(channels[3].url, channels[3].user, channels[3].pass);
}
if (channels.size() > 4 && !channels[4].url.isEmpty())
{
ui.videoWidget5->play(channels[4].url, channels[4].user, channels[4].pass);
}
if (channels.size() > 5 && !channels[5].url.isEmpty())
{
ui.videoWidget6->play(channels[5].url, channels[5].user, channels[5].pass);
}
if (channels.size() > 6 && !channels[6].url.isEmpty())
{
ui.videoWidget7->play(channels[6].url, channels[6].user, channels[6].pass);
}
if (channels.size() > 7 && !channels[7].url.isEmpty())
{
ui.videoWidget8->play(channels[7].url, channels[7].user, channels[7].pass);
}
if (channels.size() > 8 && !channels[8].url.isEmpty())
{
ui.videoWidget9->play(channels[8].url, channels[8].user, channels[8].pass);
}
}
void MediaClient::slotLayoutOne()
{
ui.widget1->show();
ui.widget2->hide();
ui.widget3->hide();
ui.widget4->hide();
ui.widget5->hide();
ui.widget6->hide();
ui.widget7->hide();
ui.widget8->hide();
ui.widget9->hide();
m_layoutMode = 1;
}
void MediaClient::slotLayoutTwo()
{
QScreen * pScreen = QApplication::primaryScreen();
Qt::ScreenOrientation orientation = pScreen->orientation();
if (orientation == Qt::LandscapeOrientation ||
orientation == Qt::InvertedLandscapeOrientation) // width > height
{
ui.widget1->show();
ui.widget2->show();
ui.widget3->hide();
ui.widget4->hide();
ui.widget5->hide();
ui.widget6->hide();
ui.widget7->hide();
ui.widget8->hide();
ui.widget9->hide();
}
else
{
ui.widget1->show();
ui.widget2->hide();
ui.widget3->hide();
ui.widget4->show();
ui.widget5->hide();
ui.widget6->hide();
ui.widget7->hide();
ui.widget8->hide();
ui.widget9->hide();
}
m_layoutMode = 2;
}
void MediaClient::slotLayoutFour()
{
ui.widget1->show();
ui.widget2->show();
ui.widget3->hide();
ui.widget4->show();
ui.widget5->show();
ui.widget6->hide();
ui.widget7->hide();
ui.widget8->hide();
ui.widget9->hide();
m_layoutMode = 4;
}
void MediaClient::slotLayoutSix()
{
QScreen * pScreen = QApplication::primaryScreen();
Qt::ScreenOrientation orientation = pScreen->orientation();
if (orientation == Qt::LandscapeOrientation ||
orientation == Qt::InvertedLandscapeOrientation) // width > height
{
ui.widget1->show();
ui.widget2->show();
ui.widget3->show();
ui.widget4->show();
ui.widget5->show();
ui.widget6->show();
ui.widget7->hide();
ui.widget8->hide();
ui.widget9->hide();
}
else
{
ui.widget1->show();
ui.widget2->show();
ui.widget3->hide();
ui.widget4->show();
ui.widget5->show();
ui.widget6->hide();
ui.widget7->show();
ui.widget8->show();
ui.widget9->hide();
}
m_layoutMode = 6;
}
void MediaClient::slotLayoutNine()
{
ui.widget1->show();
ui.widget2->show();
ui.widget3->show();
ui.widget4->show();
ui.widget5->show();
ui.widget6->show();
ui.widget7->show();
ui.widget8->show();
ui.widget9->show();
m_layoutMode = 9;
}
void MediaClient::slotLayoutChanged(int index)
{
if (0 == index)
{
slotLayoutOne();
}
else if (1 == index)
{
slotLayoutTwo();
}
else if (2 == index)
{
slotLayoutFour();
}
else if (3 == index)
{
slotLayoutSix();
}
else if (4 == index)
{
slotLayoutNine();
}
}
void MediaClient::slotWidgetSelecting(QWidget * pWidget)
{
if (m_curWidget != pWidget)
{
m_curWidget->parentWidget()->setStyleSheet("background-color: white;");
m_curWidget = (VideoWidget *) pWidget;
m_curWidget->parentWidget()->setStyleSheet("background-color: blue;");
if (m_curWidget->isRecording())
{
ui.labTipInfo->setText(tr("Recording ..."));
QIcon icon;
icon.addFile(QString(":/res/Resources/stop_record.png"));
ui.btnRecord->setIcon(icon);
}
else
{
ui.labTipInfo->setText("");
QIcon icon;
icon.addFile(QString(":/res/Resources/video_record.png"));
ui.btnRecord->setIcon(icon);
}
if (m_curWidget->isMute())
{
ui.btnVolume->setIcon(QIcon(QString::fromUtf8(":/res/Resources/mute.png")));
}
else
{
ui.btnVolume->setIcon(QIcon(QString::fromUtf8(":/res/Resources/volume.png")));
}
}
}
void MediaClient::slotPlay()
{
QString url = m_curWidget->getUrl();
QString user = m_curWidget->getUser();
QString pass = m_curWidget->getPass();
if (url.isEmpty())
{
url = m_url;
user = m_user;
pass = m_pass;
}
OpenMedia dlg(url, user, pass, this);
dlg.resize(width(), dlg.height());
if (QDialog::Accepted == dlg.exec())
{
m_url = dlg.getUrl();
m_user = dlg.getUser();
m_pass = dlg.getPass();
m_curWidget->play(m_url, m_user, m_pass);
}
}
void MediaClient::slotPause()
{
m_curWidget->pause();
}
void MediaClient::slotStop()
{
m_curWidget->stop();
ui.labTipInfo->setText(tr(""));
ui.btnVolume->setIcon(QIcon(QString::fromUtf8(":/res/Resources/volume.png")));
ui.btnRecord->setIcon(QIcon(QString::fromUtf8(":/res/Resources/video_record.png")));
}
void MediaClient::slotCallState(QWidget* pWidget, int event)
{
if (m_curWidget != pWidget)
{
return;
}
if (event == RTSP_EVE_CONNECTING ||
event == RTMP_EVE_CONNECTING ||
event == HTTP_FLV_EVE_CONNECTING ||
event == MJPEG_EVE_CONNECTING ||
event == SRT_EVE_CONNECTING)
{
ui.labTipInfo->setText(tr("Connecting..."));
}
else if (event == RTSP_EVE_CONNFAIL ||
event == RTMP_EVE_CONNFAIL ||
event == HTTP_FLV_EVE_CONNFAIL ||
event == MJPEG_EVE_CONNFAIL ||
event == SRT_EVE_CONNFAIL)
{
ui.labTipInfo->setText(tr("Connect failed"));
}
else if (event == RTSP_EVE_CONNSUCC ||
event == MJPEG_EVE_CONNSUCC ||
event == RTMP_EVE_VIDEOREADY ||
event == RTMP_EVE_AUDIOREADY ||
event == HTTP_FLV_EVE_VIDEOREADY ||
event == HTTP_FLV_EVE_AUDIOREADY ||
event == SRT_EVE_VIDEOREADY ||
event == SRT_EVE_AUDIOREADY)
{
ui.labTipInfo->setText(tr(""));
}
else if (event == RTSP_EVE_NOSIGNAL ||
event == RTMP_EVE_NOSIGNAL ||
event == HTTP_FLV_EVE_NOSIGNAL ||
event == MJPEG_EVE_NOSIGNAL ||
event == SRT_EVE_NOSIGNAL)
{
ui.labTipInfo->setText(tr("NO Signal"));
}
else if (event == RTSP_EVE_NODATA ||
event == RTMP_EVE_NODATA ||
event == HTTP_FLV_EVE_NODATA ||
event == MJPEG_EVE_NODATA ||
event == SRT_EVE_NODATA)
{
ui.labTipInfo->setText(tr("No Data"));
}
else if (event == RTSP_EVE_RESUME ||
event == RTMP_EVE_RESUME ||
event == HTTP_FLV_EVE_RESUME ||
event == MJPEG_EVE_RESUME ||
event == SRT_EVE_RESUME)
{
ui.labTipInfo->setText(tr(""));
}
else if (event == RTSP_EVE_AUTHFAILED ||
event == RTMP_EVE_AUTHFAILED||
event == HTTP_FLV_EVE_AUTHFAILED ||
event == MJPEG_EVE_AUTHFAILED ||
event == SRT_EVE_AUTHFAILED)
{
ui.labTipInfo->setText(tr("Authenticate failed"));
}
}
void MediaClient::slotSnapshot()
{
m_curWidget->snapshot();
}
void MediaClient::slotRecord()
{
m_curWidget->record();
if (m_curWidget->isRecording())
{
ui.labTipInfo->setText(tr(" Recording ..."));
ui.btnRecord->setIcon(QIcon(QString::fromUtf8(":/res/Resources/stop_record.png")));
}
else
{
ui.labTipInfo->setText("");
ui.btnRecord->setIcon(QIcon(QString::fromUtf8(":/res/Resources/video_record.png")));
}
}
void MediaClient::slotSnapshotResult(bool ret)
{
QString msg;
if (ret)
{
msg = tr("Snapshot success");
}
else
{
msg = tr("Snapshot failed");
}
InstMsgDialog dlg(msg, 2000);
dlg.exec();
}
void MediaClient::slotRecordResult(bool ret)
{
ui.btnRecord->setIcon(QIcon(QString::fromUtf8(":/res/Resources/video_record.png")));
QString msg;
if (ret)
{
msg = tr("Video recording success");
}
else
{
msg = tr("Video recording failed");
}
InstMsgDialog dlg(msg, 2000);
dlg.exec();
}
void MediaClient::slotVolume()
{
if (!m_curWidget->isPlaying())
{
return;
}
if (m_curWidget->isMute())
{
ui.btnVolume->setIcon(QIcon(QString::fromUtf8(":/res/Resources/volume.png")));
m_curWidget->setMute(FALSE);
}
else
{
ui.btnVolume->setIcon(QIcon(QString::fromUtf8(":/res/Resources/mute.png")));
m_curWidget->setMute(TRUE);
}
}
void MediaClient::slotUpdateStatistics(int bytes)
{
int kb = 0, mb = 0;
char buff[100];
m_recvBytes += bytes;
if (m_recvBytes >= 1000)
{
kb = m_recvBytes / 1000;
}
if (kb >= 1000)
{
mb = kb / 1000;
kb = kb % 1000;
kb = kb / 10;
sprintf(buff, " %d.%02d MB", mb, kb);
}
else
{
sprintf(buff, " %d KB", kb);
}
ui.labStatistics->setText(tr("RX:") + buff);
}
void MediaClient::slotOrientationChanged(Qt::ScreenOrientation orientation)
{
setupLayout(orientation);
}
void MediaClient::setupLayout(Qt::ScreenOrientation orientation)
{
log_print(HT_LOG_DBG, "setupLayout, orientation = %d\r\n", orientation);
if (orientation == Qt::LandscapeOrientation ||
orientation == Qt::InvertedLandscapeOrientation) // width > height
{
ui.infoWidget->layout()->removeWidget(ui.cmbLayout);
ui.infoWidget->layout()->removeWidget(ui.labTipInfo);
ui.infoWidget->layout()->removeWidget(ui.labStatistics);
ui.infoWidget->hide();
ui.toolWidget->layout()->removeItem(ui.horizontalSpacer1);
ui.toolWidget->layout()->removeWidget(ui.btnPlay);
ui.toolWidget->layout()->removeWidget(ui.btnPause);
ui.toolWidget->layout()->removeWidget(ui.btnStop);
ui.toolWidget->layout()->removeItem(ui.horizontalSpacer2);
ui.toolWidget->layout()->removeWidget(ui.btnVolume);
ui.toolWidget->layout()->removeWidget(ui.btnSnapshot);
ui.toolWidget->layout()->removeWidget(ui.btnRecord);
ui.toolWidget->layout()->removeItem(ui.horizontalSpacer3);
ui.toolWidget->layout()->addWidget(ui.cmbLayout);
ui.toolWidget->layout()->addItem(ui.horizontalSpacer1);
ui.toolWidget->layout()->addWidget(ui.btnPlay);
ui.toolWidget->layout()->addWidget(ui.btnPause);
ui.toolWidget->layout()->addWidget(ui.btnStop);
ui.toolWidget->layout()->addItem(ui.horizontalSpacer2);
ui.toolWidget->layout()->addWidget(ui.btnVolume);
ui.toolWidget->layout()->addWidget(ui.btnSnapshot);
ui.toolWidget->layout()->addWidget(ui.btnRecord);
ui.toolWidget->layout()->addItem(ui.horizontalSpacer3);
ui.toolWidget->layout()->addWidget(ui.labStatistics);
}
else // height > width
{
ui.infoWidget->layout()->removeWidget(ui.cmbLayout);
ui.infoWidget->layout()->removeWidget(ui.labTipInfo);
ui.infoWidget->layout()->removeWidget(ui.labStatistics);
ui.toolWidget->layout()->removeWidget(ui.labTipInfo);
ui.toolWidget->layout()->removeWidget(ui.labStatistics);
ui.toolWidget->layout()->removeWidget(ui.btnPlay);
ui.toolWidget->layout()->removeWidget(ui.btnPause);
ui.toolWidget->layout()->removeWidget(ui.btnStop);
ui.toolWidget->layout()->removeWidget(ui.btnVolume);
ui.toolWidget->layout()->removeWidget(ui.btnSnapshot);
ui.toolWidget->layout()->removeWidget(ui.btnRecord);
ui.toolWidget->layout()->removeItem(ui.horizontalSpacer1);
ui.toolWidget->layout()->removeItem(ui.horizontalSpacer2);
ui.toolWidget->layout()->removeItem(ui.horizontalSpacer3);
ui.infoWidget->layout()->addWidget(ui.cmbLayout);
ui.infoWidget->layout()->addWidget(ui.labTipInfo);
ui.infoWidget->layout()->addWidget(ui.labStatistics);
ui.infoWidget->show();
ui.toolWidget->layout()->addItem(ui.horizontalSpacer1);
ui.toolWidget->layout()->addWidget(ui.btnPlay);
ui.toolWidget->layout()->addWidget(ui.btnPause);
ui.toolWidget->layout()->addWidget(ui.btnStop);
ui.toolWidget->layout()->addItem(ui.horizontalSpacer2);
ui.toolWidget->layout()->addWidget(ui.btnVolume);
ui.toolWidget->layout()->addWidget(ui.btnSnapshot);
ui.toolWidget->layout()->addWidget(ui.btnRecord);
ui.toolWidget->layout()->addItem(ui.horizontalSpacer3);
}
if (m_layoutMode == 2)
{
slotLayoutTwo();
}
else if (m_layoutMode == 6)
{
slotLayoutSix();
}
}
void MediaClient::slotSystemSetting()
{
SystemSetting dlg(m_syscfg, this);
if (QDialog::Accepted == dlg.exec())
{
SysConfig config = dlg.getSysConfig();
/* apply system config parameter */
if (m_syscfg.enableLog != config.enableLog)
{
if (m_syscfg.enableLog)
{
log_close();
}
else
{
initLog();
}
}
log_set_level(config.logLevel);
/* update system config parameter */
m_syscfg.enableLog = config.enableLog;
m_syscfg.videoRenderMode = config.videoRenderMode;
m_syscfg.rtpMulticast = config.rtpMulticast;
m_syscfg.rtpOverUdp = config.rtpOverUdp;
m_syscfg.rtspOverHttp = config.rtspOverHttp;
m_syscfg.rtspOverHttpPort = config.rtspOverHttpPort;
m_syscfg.rtspOverWs = config.rtspOverWs;
m_syscfg.rtspOverWsPort = config.rtspOverWsPort;
m_syscfg.logLevel = config.logLevel;
m_syscfg.hwDecoding = config.hwDecoding;
m_syscfg.recordTime = config.recordTime;
m_syscfg.recordSize = config.recordSize;
}
}
void MediaClient::slotOpenSnapshot()
{
FileBrowse * pDlg = new FileBrowse(FILE_FORMAT_PIC, this);
pDlg->exec();
delete pDlg;
}
void MediaClient::slotOpenRecording()
{
FileBrowse * pDlg = new FileBrowse(FILE_FORMAT_VIDEO, this);
pDlg->exec();
delete pDlg;
}
void MediaClient::slotAbout()
{
About dlg(this);
dlg.exec();
}

View File

@@ -0,0 +1,90 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#ifndef MEDIACLIENT_H
#define MEDIACLIENT_H
#include <QDialog>
#include "ui_MediaClient.h"
#include "config.h"
class MediaClient : public QDialog
{
Q_OBJECT
public:
MediaClient(QWidget *parent = Q_NULLPTR, Qt::WindowFlags flags = Qt::Widget);
~MediaClient();
private slots:
void slotLayoutOne();
void slotLayoutTwo();
void slotLayoutFour();
void slotLayoutSix();
void slotLayoutNine();
void slotLayoutChanged(int index);
void slotPlay();
void slotPause();
void slotStop();
void slotCallState(QWidget* pWidget, int event);
void slotSnapshot();
void slotRecord();
void slotVolume();
void slotSnapshotResult(bool ret);
void slotRecordResult(bool ret);
void slotUpdateStatistics(int bytes);
void slotOrientationChanged(Qt::ScreenOrientation orientation);
void slotWidgetSelecting(QWidget * pWidget);
void slotSystemSetting();
void slotOpenSnapshot();
void slotOpenRecording();
void slotAbout();
protected:
void closeEvent(QCloseEvent * event);
private:
void initLog();
void initDialog();
void connSignalSlot();
void saveChannel(QList<CHANNEL> &channels, VideoWidget * widget);
void connVideoWidget(VideoWidget * widget);
void showBitrate(int bitrate);
void setupLayout(Qt::ScreenOrientation orientation);
void loadSystemConfig();
void loadChannels();
private:
Ui::MediaClient ui;
VideoWidget * m_curWidget;
int m_layoutMode;
uint64 m_recvBytes;
QString m_url;
QString m_user;
QString m_pass;
SysConfig m_syscfg;
};
#endif

View File

@@ -0,0 +1,69 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#include "sys_inc.h"
#include "OpenMedia.h"
#include "utils.h"
#include <QMessageBox>
#include <QFileDialog>
OpenMedia::OpenMedia(QString &url, QString &user, QString &pass, QWidget *parent, Qt::WindowFlags flags)
: QDialog(parent, flags)
, m_url(url)
, m_user(user)
, m_pass(pass)
{
ui.setupUi(this);
ui.editUrl->setText(m_url);
ui.editUser->setText(m_user);
ui.editPass->setText(m_pass);
connect(ui.btnCancel, SIGNAL(clicked()), this, SLOT(close()));
connect(ui.btnConfirm, SIGNAL(clicked()), this, SLOT(slotConfirm()));
}
OpenMedia::~OpenMedia()
{
}
void OpenMedia::slotConfirm()
{
m_url = ui.editUrl->text();
m_user = ui.editUser->text();
m_pass = ui.editPass->text();
if (m_url.isEmpty())
{
ui.editUrl->setFocus();
return;
}
else if (!isUrl(m_url))
{
QMessageBox::information(NULL, tr("Tips"), tr("Invalid URL format!"));
ui.editUrl->setFocus();
return;
}
accept();
}

View File

@@ -0,0 +1,49 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#ifndef OPEN_MEDIA_H
#define OPEN_MEDIA_H
#include <QDialog>
#include "ui_OpenMedia.h"
class OpenMedia : public QDialog
{
Q_OBJECT
public:
OpenMedia(QString &url, QString &user, QString &pass, QWidget *parent = 0, Qt::WindowFlags flags = Qt::Widget);
~OpenMedia();
QString getUrl() { return m_url; }
QString getUser() { return m_user; }
QString getPass() { return m_pass; }
public slots:
void slotConfirm();
private:
Ui::OpenMedia ui;
QString m_url;
QString m_user;
QString m_pass;
};
#endif

View File

@@ -0,0 +1,128 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#include "SystemSetting.h"
#include "utils.h"
#include "video_decoder.h"
#include <QFile>
#include <QMessageBox>
#include <QDesktopServices>
#include <QFileDialog>
#include <QScreen>
SystemSetting::SystemSetting(SysConfig &config, QWidget *parent)
: QDialog(parent)
, m_syscfg(config)
{
ui.setupUi(this);
initDialog();
connSignalSlot();
}
SystemSetting::~SystemSetting()
{
}
void SystemSetting::initDialog()
{
ui.cmbLogLevel->addItem(tr("TRACE"));
ui.cmbLogLevel->addItem(tr("DEBUG"));
ui.cmbLogLevel->addItem(tr("INFO"));
ui.cmbLogLevel->addItem(tr("WARNING"));
ui.cmbLogLevel->addItem(tr("ERROR"));
ui.cmbLogLevel->addItem(tr("FATAL"));
ui.cmbVideoRenderMode->addItem(tr("Keep the original aspect ratio"));
ui.cmbVideoRenderMode->addItem(tr("Fill the whole window"));
#if defined(ANDROID)
ui.cmbHWDecoding->addItem(tr("Automatic"), HW_DECODING_AUTO);
ui.cmbHWDecoding->addItem(tr("Media Codec"), HW_DECODING_MEDIACODEC);
ui.cmbHWDecoding->addItem(tr("Disable"), HW_DECODING_DISABLE);
#elif defined(IOS)
ui.cmbHWDecoding->addItem(tr("Automatic"), HW_DECODING_AUTO);
ui.cmbHWDecoding->addItem(tr("Videotoolbox"), HW_DECODING_VIDEOTOOLBOX);
ui.cmbHWDecoding->addItem(tr("Disable"), HW_DECODING_DISABLE);
#endif
ui.chkEnableLog->setChecked(m_syscfg.enableLog);
ui.chkRtpMulticast->setChecked(m_syscfg.rtpMulticast);
ui.chkRtpOverUdp->setChecked(m_syscfg.rtpOverUdp);
ui.chkRtspOverHttp->setChecked(m_syscfg.rtspOverHttp);
ui.spinHttpPort->setValue(m_syscfg.rtspOverHttpPort);
ui.chkRtspOverWs->setChecked(m_syscfg.rtspOverWs);
ui.spinWsPort->setValue(m_syscfg.rtspOverWsPort);
ui.cmbVideoRenderMode->setCurrentIndex(m_syscfg.videoRenderMode);
ui.cmbLogLevel->setCurrentIndex(m_syscfg.logLevel);
ui.timeRecordTime->setTime(QTime(0,0,0,0).addSecs(m_syscfg.recordTime));
ui.spinRecordSize->setValue(m_syscfg.recordSize);
for (int i = 0; i < ui.cmbHWDecoding->count(); i++)
{
if (ui.cmbHWDecoding->itemData(i).toInt() == m_syscfg.hwDecoding)
{
ui.cmbHWDecoding->setCurrentIndex(i);
break;
}
}
showMaximized();
}
void SystemSetting::connSignalSlot()
{
connect(ui.btnCancel, SIGNAL(clicked()), this, SLOT(close()));
connect(ui.btnConfirm, SIGNAL(clicked()), this, SLOT(slotConfirm()));
}
void SystemSetting::slotConfirm()
{
m_syscfg.enableLog = ui.chkEnableLog->isChecked();
m_syscfg.logLevel = ui.cmbLogLevel->currentIndex();
m_syscfg.rtpMulticast = ui.chkRtpMulticast->isChecked();
m_syscfg.rtpOverUdp = ui.chkRtpOverUdp->isChecked();
m_syscfg.rtspOverHttp = ui.chkRtspOverHttp->isChecked();
m_syscfg.rtspOverHttpPort = ui.spinHttpPort->value();
m_syscfg.rtspOverWs = ui.chkRtspOverWs->isChecked();
m_syscfg.rtspOverWsPort = ui.spinWsPort->value();
m_syscfg.recordTime = QTime(0, 0, 0, 0).secsTo(ui.timeRecordTime->time());
m_syscfg.recordSize = ui.spinRecordSize->value();
m_syscfg.videoRenderMode = ui.cmbVideoRenderMode->currentIndex();
m_syscfg.hwDecoding = ui.cmbHWDecoding->currentData().toInt();
saveEnableLogFlag(m_syscfg.enableLog);
saveLogLevel(m_syscfg.logLevel);
saveRtpMulticast(m_syscfg.rtpMulticast);
saveRtpOverUdp(m_syscfg.rtpOverUdp);
saveRtspOverHttp(m_syscfg.rtspOverHttp);
saveRtspOverHttpPort(m_syscfg.rtspOverHttpPort);
saveRtspOverWs(m_syscfg.rtspOverWs);
saveRtspOverWsPort(m_syscfg.rtspOverWsPort);
saveRecordTime(m_syscfg.recordTime);
saveRecordSize(m_syscfg.recordSize);
saveVideoRenderMode(m_syscfg.videoRenderMode);
saveHWDecoding(m_syscfg.hwDecoding);
accept();
}

View File

@@ -0,0 +1,56 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#ifndef _SYSTEM_SETTING_H_
#define _SYSTEM_SETTING_H_
#include "sys_inc.h"
#include <QDialog>
#include "ui_SystemSetting.h"
#include "config.h"
class SystemSetting : public QDialog
{
Q_OBJECT
public:
SystemSetting(SysConfig &config, QWidget *parent = 0);
~SystemSetting();
SysConfig getSysConfig(){return m_syscfg;}
private slots:
void slotConfirm();
private:
void initDialog();
void connSignalSlot();
private:
Ui::SystemSetting ui;
SysConfig m_syscfg;
};
#endif

View File

@@ -0,0 +1,732 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#include "sys_inc.h"
#include "VideoWidget.h"
#include "utils.h"
#include "InstMsgDialog.h"
#include "rtsp_player.h"
#include "rtmp_player.h"
#include "http_flv_player.h"
#include "http_mjpeg_player.h"
#include "srt_player.h"
#include "file_player.h"
#include "http_test.h"
#include <QPainter>
#include <QMessageBox>
#include <QScreen>
#include <QPaintEvent>
#include <QDir>
#include <QFile>
#include <QDateTime>
#include <QApplication>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#if defined(ANDROID)
#include <QJniObject>
#endif
/*********************************************************************************************/
#define VERTEXIN 0
#define TEXTUREIN 1
/*********************************************************************************************/
VideoWidget::VideoWidget(QWidget * parent, Qt::WindowFlags f)
: QOpenGLWidget(parent, f)
, m_pPlayer(NULL)
, m_bMute(FALSE)
, m_bRecording(FALSE)
, m_pRenderFrame(NULL)
#ifdef BACKCHANNEL
, m_nBackChannelFlag(0)
#endif
{
setAttribute(Qt::WA_OpaquePaintEvent);
m_timerReconn.setSingleShot(true);
connect(&m_timerReconn, SIGNAL(timeout()), this, SLOT(slotReconn()));
connect(this, SIGNAL(imageReady()), this, SLOT(update()), Qt::QueuedConnection);
}
VideoWidget::~VideoWidget()
{
closeVideo();
makeCurrent();
vbo.destroy();
textureY->destroy();
textureU->destroy();
textureV->destroy();
doneCurrent();
}
void VideoWidget::play(QString url, QString acct, QString pass)
{
if (m_url == url && m_acct == acct && m_pass == pass)
{
return;
}
closeVideo();
m_url = url;
m_acct = acct;
m_pass = pass;
m_base = getBaseName(url);
if (!m_url.isEmpty())
{
makeCall();
}
}
void VideoWidget::pause()
{
if (m_pPlayer)
{
m_pPlayer->pause();
}
}
void VideoWidget::stop()
{
closeVideo();
}
void VideoWidget::closePlayer()
{
m_timerReconn.stop();
if (m_pPlayer)
{
delete m_pPlayer;
m_pPlayer = NULL;
}
}
void VideoWidget::closeVideo()
{
closePlayer();
m_url = "";
m_acct = "";
m_pass = "";
m_bRecording = FALSE;
QMutexLocker locker(&m_mutex);
if (m_pRenderFrame)
{
av_frame_free(&m_pRenderFrame);
}
update();
}
BOOL VideoWidget::isRecording()
{
if (m_pPlayer)
{
return m_pPlayer->isRecording();
}
return FALSE;
}
void VideoWidget::makeCall()
{
BOOL isFile = 0;
BOOL isRtsp = FALSE;
if (isRtspUrl(m_url))
{
isRtsp = TRUE;
m_pPlayer = new CRtspPlayer(this);
}
else if (isRtmpUrl(m_url))
{
m_pPlayer = new CRtmpPlayer(this);
}
else if (isHttpUrl(m_url))
{
HTTPCTT ctt;
if (http_test(m_url.toStdString().c_str(), m_acct.toStdString().c_str(), m_pass.toStdString().c_str(), &ctt, 2*1000))
{
if (CTT_RTSP_TUNNELLED == ctt)
{
isRtsp = TRUE;
m_pPlayer = new CRtspPlayer(this);
}
else if (CTT_FLV == ctt)
{
m_pPlayer = new CHttpFlvPlayer(this);
}
else if (CTT_MULTIPART == ctt)
{
m_pPlayer = new CHttpMjpegPlayer(this);
}
}
else
{
m_pPlayer = new CHttpFlvPlayer(this);
}
}
else if (isSrtUrl(m_url))
{
m_pPlayer = new CSrtPlayer(this);
}
else
{
isFile = 1;
m_pPlayer = new CFilePlayer(this);
}
if (NULL == m_pPlayer)
{
return;
}
connect(m_pPlayer, SIGNAL(notify(int)), this, SLOT(slotPlayerNotify(int)), Qt::QueuedConnection);
connect(m_pPlayer, SIGNAL(snapshoted(AVFrame*)), this, SLOT(slotSnapshoted(AVFrame*)), Qt::QueuedConnection);
connect(m_pPlayer, SIGNAL(imageReady(AVFrame*)), this, SLOT(slotImageReady(AVFrame*)), Qt::QueuedConnection);
connect(m_pPlayer, SIGNAL(updateStatistics(int)), this, SIGNAL(updateStatistics(int)));
if (m_pPlayer->open(m_url, 0))
{
m_pPlayer->setAuthInfo(m_acct, m_pass);
m_pPlayer->setHWDecoding(getHWDecoding());
if (isRtsp)
{
m_pPlayer->setRtpOverUdp(getRtpOverUdp());
m_pPlayer->setRtpMulticast(getRtpMulticast());
#ifdef OVER_HTTP
m_pPlayer->setRtspOverHttp(getRtspOverHttp(), getRtspOverHttpPort());
#endif
#ifdef OVER_WEBSOCKET
m_pPlayer->setRtspOverWs(getRtspOverWs(), getRtspOverWsPort());
#endif
#ifdef BACKCHANNEL
m_pPlayer->setBCFlag(m_nBackChannelFlag);
#endif
}
else if (isFile)
{
setMute(m_bMute);
}
m_pPlayer->play();
}
else
{
closePlayer();
}
}
void VideoWidget::mousePressEvent(QMouseEvent * event)
{
emit widgetSelecting(this);
}
BOOL VideoWidget::micphone()
{
#ifdef BACKCHANNEL
if (NULL == m_pPlayer)
{
return FALSE;
}
#if defined(ANDROID)
QJniObject str = QJniObject::fromString("android.permission.RECORD_AUDIO");
QJniObject::callStaticMethod<jint>("org/happytimesoft/util/HtUtil",
"requestPermission",
"(Landroid/content/Context;Ljava/lang/String;)I",
QNativeInterface::QAndroidApplication::context(),
str.object<jstring>());
#endif
closePlayer();
if (m_nBackChannelFlag)
{
m_nBackChannelFlag = 0;
}
else
{
m_nBackChannelFlag = 1;
}
makeCall();
if (m_pPlayer)
{
m_pPlayer->setBCDataFlag(m_nBackChannelFlag);
return m_pPlayer->getBCFlag();
}
#endif
return FALSE;
}
void VideoWidget::setMute(BOOL flag)
{
m_bMute = flag;
if (m_pPlayer)
{
m_pPlayer->setVolume(flag ? HTVOLUME_MIN : HTVOLUME_MAX);
}
}
BOOL VideoWidget::isPlaying()
{
if (m_pPlayer)
{
return m_pPlayer->isPlaying() || m_pPlayer->isPaused();
}
return FALSE;
}
void VideoWidget::snapshot()
{
if (m_pPlayer)
{
m_pPlayer->snapshot(VIDEO_FMT_RGB24);
}
}
void VideoWidget::slotSnapshoted(AVFrame * frame)
{
QImage image = QImage(frame->data[0], frame->width, frame->height, frame->linesize[0], QImage::Format_RGB888);
QString file = getSnapshotPath() + "/" + getTempFile(m_base, ".jpg");
if (!image.save(file, "JPG"))
{
emit snapshotResult(false);
}
else
{
emit snapshotResult(true);
}
}
BOOL VideoWidget::record()
{
if (NULL == m_pPlayer)
{
return FALSE;
}
if (m_pPlayer->isRecording())
{
stopRecord();
}
else
{
startRecord();
}
m_bRecording = m_pPlayer->isRecording();
return m_bRecording;
}
void VideoWidget::startRecord()
{
if (NULL == m_pPlayer)
{
return;
}
if (m_pPlayer->isRecording())
{
return;
}
QString file = getRecordPath() + "/" + getTempFile(m_base, ".avi");
m_pPlayer->record(file);
}
void VideoWidget::stopRecord()
{
if (NULL == m_pPlayer)
{
return;
}
if (m_pPlayer->isRecording())
{
m_pPlayer->stopRecord();
emit recordResult(true);
}
}
QRect VideoWidget::getVideoRenderRect(int videoW, int videoH)
{
QRect rect = this->rect();
qreal ratio = QGuiApplication::primaryScreen()->devicePixelRatio();
int w = rect.width() * ratio;
int h = rect.height() * ratio;
if (getVideoRenderMode() == RENDER_MODE_KEEP) // keep the original aspect ratio
{
int iw = videoW;
int ih = videoH;
int nw, nh;
double vratio = iw / (double)ih;
double wratio = w / (double)h;
if (vratio > wratio)
{
nw = w;
nh = w * ih / iw;
}
else
{
nw = h * iw / ih;
nh = h;
}
rect.setLeft((w - nw) / 2);
rect.setTop((h - nh) / 2);
rect.setRight(rect.left() + nw);
rect.setBottom(rect.top() + nh);
}
else // fill the whole window
{
rect.setLeft(0);
rect.setTop(0);
rect.setRight(w);
rect.setBottom(h);
}
return rect;
}
void VideoWidget::slotImageReady(AVFrame * frame)
{
QMutexLocker locker(&m_mutex);
if (m_pRenderFrame)
{
av_frame_free(&m_pRenderFrame);
}
if (m_pPlayer)
{
m_pRenderFrame = frame;
emit imageReady();
}
else
{
av_frame_free(&frame);
}
}
void VideoWidget::slotPlayerNotify(int event)
{
if (event == RTSP_EVE_CONNFAIL ||
event == RTMP_EVE_CONNFAIL ||
event == HTTP_FLV_EVE_CONNFAIL ||
event == MJPEG_EVE_CONNFAIL ||
event == SRT_EVE_CONNFAIL)
{
m_timerReconn.start(5 * 1000);
}
else if (event == RTSP_EVE_CONNSUCC ||
event == MJPEG_EVE_CONNSUCC)
{
setMute(m_bMute);
// Re-record after reconnect
if (m_bRecording)
{
startRecord();
}
}
else if (event == RTMP_EVE_VIDEOREADY ||
event == HTTP_FLV_EVE_VIDEOREADY ||
event == SRT_EVE_VIDEOREADY)
{
// Re-record after reconnect
if (m_bRecording)
{
startRecord();
}
}
else if (event == RTMP_EVE_AUDIOREADY ||
event == HTTP_FLV_EVE_AUDIOREADY ||
event == SRT_EVE_AUDIOREADY)
{
setMute(m_bMute);
}
else if (event == RTSP_EVE_NOSIGNAL ||
event == RTMP_EVE_NOSIGNAL ||
event == HTTP_FLV_EVE_NOSIGNAL ||
event == MJPEG_EVE_NOSIGNAL ||
event == SRT_EVE_NOSIGNAL)
{
m_timerReconn.start(5 * 1000);
}
else if (event == RTSP_EVE_NODATA ||
event == RTMP_EVE_NODATA ||
event == HTTP_FLV_EVE_NODATA ||
event == MJPEG_EVE_NODATA ||
event == SRT_EVE_NODATA)
{
m_timerReconn.start(5 * 1000);
}
else if (event == RTSP_EVE_RESUME ||
event == RTMP_EVE_RESUME ||
event == HTTP_FLV_EVE_RESUME ||
event == MJPEG_EVE_RESUME ||
event == SRT_EVE_RESUME)
{
m_timerReconn.stop();
}
else if (event == RTSP_EVE_STOPPED ||
event == RTMP_EVE_STOPPED ||
event == HTTP_FLV_EVE_STOPPED ||
event == MJPEG_EVE_STOPPED ||
event == SRT_EVE_STOPPED)
{
m_timerReconn.start(5 * 1000);
}
emit callState(this, event);
}
void VideoWidget::slotReconn()
{
closePlayer();
makeCall();
}
QString VideoWidget::getBaseName(QString &url)
{
if (isUrl(url))
{
char host[100] = {'\0'};
url_split(url.toStdString().c_str(), NULL, 0, NULL, 0, NULL, 0, host, sizeof(host), NULL, NULL, 0);
return QString(host);
}
else
{
QFileInfo fileInfo(url);
return fileInfo.baseName();
}
}
void VideoWidget::initializeGL()
{
initializeOpenGLFunctions();
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
static const GLfloat vertices[]
{
-1.0f, -1.0f,
-1.0f, +1.0f,
+1.0f, +1.0f,
+1.0f, -1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
};
if (!vbo.create())
{
log_print(HT_LOG_ERR, "%s, vbo.create failed\r\n", __FUNCTION__);
}
if (!vbo.bind())
{
log_print(HT_LOG_ERR, "%s, vbo.bind failed\r\n", __FUNCTION__);
}
vbo.allocate(vertices, sizeof(vertices));
QOpenGLShader * vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
const char * vsrc =
"attribute vec4 vertexIn; \n"
"attribute vec2 textureIn; \n"
"varying highp vec2 textureOut; \n"
"void main(void) \n"
"{ \n"
" gl_Position = vertexIn; \n"
" textureOut = textureIn; \n"
"}";
if (!vshader->compileSourceCode(vsrc))
{
log_print(HT_LOG_ERR, "%s, compile vertex source failed\r\n", __FUNCTION__);
}
QOpenGLShader * fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
const char * fsrc =
"varying highp vec2 textureOut; \n"
"uniform sampler2D tex_y; \n"
"uniform sampler2D tex_u; \n"
"uniform sampler2D tex_v; \n"
"void main(void) \n"
"{ \n"
" lowp vec3 yuv; \n"
" lowp vec3 rgb; \n"
" yuv.x = texture2D(tex_y, textureOut).r; \n"
" yuv.y = texture2D(tex_u, textureOut).r - 0.5; \n"
" yuv.z = texture2D(tex_v, textureOut).r - 0.5; \n"
" rgb = mat3( 1, 1, 1, \n"
" 0, -0.39465, 2.03211, \n"
" 1.13983, -0.58060, 0) * yuv; \n"
" gl_FragColor = vec4(rgb, 1); \n"
"}";
if (!fshader->compileSourceCode(fsrc))
{
log_print(HT_LOG_ERR, "%s, compile fragment source failed\r\n", __FUNCTION__);
}
program = new QOpenGLShaderProgram(this);
if (!program->addShader(vshader))
{
log_print(HT_LOG_ERR, "%s, add vertex shader failed\r\n", __FUNCTION__);
}
if (!program->addShader(fshader))
{
log_print(HT_LOG_ERR, "%s, add fragment shader failed\r\n", __FUNCTION__);
}
program->bindAttributeLocation("vertexIn", VERTEXIN);
program->bindAttributeLocation("textureIn", TEXTUREIN);
if (!program->link())
{
log_print(HT_LOG_ERR, "%s, link failed. %s\r\n", __FUNCTION__, program->log().toStdString().c_str());
}
if (!program->bind())
{
log_print(HT_LOG_ERR, "%s, program bind failed\r\n", __FUNCTION__);
}
program->enableAttributeArray(VERTEXIN);
program->enableAttributeArray(TEXTUREIN);
program->setAttributeBuffer(VERTEXIN, GL_FLOAT, 0, 2, 2*sizeof(GLfloat));
program->setAttributeBuffer(TEXTUREIN, GL_FLOAT, 8*sizeof(GLfloat), 2, 2*sizeof(GLfloat));
textureUniformY = program->uniformLocation("tex_y");
textureUniformU = program->uniformLocation("tex_u");
textureUniformV = program->uniformLocation("tex_v");
textureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
textureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
textureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
if (!textureY->create())
{
log_print(HT_LOG_ERR, "%s, textureY create failed\r\n", __FUNCTION__);
}
if (!textureU->create())
{
log_print(HT_LOG_ERR, "%s, textureU create failed\r\n", __FUNCTION__);
}
if (!textureV->create())
{
log_print(HT_LOG_ERR, "%s, textureV create failed\r\n", __FUNCTION__);
}
idY = textureY->textureId();
idU = textureU->textureId();
idV = textureV->textureId();
glClearColor(0.0, 0.0, 0.0, 1.0f);
}
void VideoWidget::paintGL()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
QMutexLocker locker(&m_mutex);
if (NULL == m_pRenderFrame)
{
return;
}
int videoW = m_pRenderFrame->width;
int videoH = m_pRenderFrame->height;
QRect rect = getVideoRenderRect(videoW, videoH);
glViewport(rect.left(), rect.top(), rect.width(), rect.height());
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, idY);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, videoW, videoH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pRenderFrame->data[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, idU);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, videoW >> 1, videoH >> 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pRenderFrame->data[1]);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, idV);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, videoW >> 1, videoH >> 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_pRenderFrame->data[2]);
glUniform1i(textureUniformY, 0);
glUniform1i(textureUniformU, 1);
glUniform1i(textureUniformV, 2);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

View File

@@ -0,0 +1,117 @@
/***************************************************************************************
*
* IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
*
* By downloading, copying, installing or using the software you agree to this license.
* If you do not agree to this license, do not download, install,
* copy or use the software.
*
* Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
*
* Redistribution and use in binary forms, with or without modification, are permitted.
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************************/
#ifndef VIDEO_WIDGET_H
#define VIDEO_WIDGET_H
#include "sys_inc.h"
#include "video_player.h"
#include <QWidget>
#include <QMutex>
#include <QSwipeGesture>
#include <QGestureEvent>
#include <QTimer>
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QOpenGLShaderProgram>
class VideoWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
VideoWidget(QWidget * parent = 0, Qt::WindowFlags f = Qt::Widget);
~VideoWidget();
void play(QString url, QString user, QString pass);
void pause();
void stop();
void snapshot();
BOOL record();
void stopRecord();
void setMute(BOOL flag);
BOOL micphone();
BOOL isPlaying();
BOOL isMute() {return m_bMute;}
BOOL isRecording();
QString getUrl() { return m_url; }
QString getUser() { return m_acct; }
QString getPass() { return m_pass; }
private slots:
void slotImageReady(AVFrame * frame);
void slotPlayerNotify(int event);
void slotReconn();
void slotSnapshoted(AVFrame * frame);
signals:
void callState(QWidget *, int);
void snapshotResult(bool);
void recordResult(bool);
void updateStatistics(int);
void imageReady();
void widgetSelecting(QWidget *);
protected:
void mousePressEvent(QMouseEvent * event);
void initializeGL();
void paintGL();
private:
void closePlayer();
void closeVideo();
void makeCall();
QRect getVideoRenderRect(int videoW, int videoH);
void startRecord();
QString getBaseName(QString &url);
private:
QString m_url;
QString m_acct;
QString m_pass;
QString m_base;
CVideoPlayer * m_pPlayer;
QTimer m_timerReconn;
BOOL m_bMute;
BOOL m_bRecording;
QMutex m_mutex;
AVFrame * m_pRenderFrame;
#ifdef BACKCHANNEL
int m_nBackChannelFlag;
#endif
QOpenGLShaderProgram * program;
QOpenGLBuffer vbo;
GLuint textureUniformY;
GLuint textureUniformU;
GLuint textureUniformV;
QOpenGLTexture* textureY = nullptr;
QOpenGLTexture* textureU = nullptr;
QOpenGLTexture* textureV = nullptr;
GLuint idY, idU, idV;
};
#endif

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>${ASSETCATALOG_COMPILER_APPICON_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${QMAKE_SHORT_VERSION}</string>
<key>CFBundleVersion</key>
<string>${QMAKE_FULL_VERSION}</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NOTE</key>
<string>This file was generated by Qt/QMake.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSLocalNetworkUsageDescription</key>
<string>connect to server</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen.storyboard</string>
<key>UIRequiresPersistentWiFi</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,3 @@
#import <UIKit/UIKit.h>
@interface DocViewController : UIViewController <UIDocumentInteractionControllerDelegate>
@end

View File

@@ -0,0 +1,20 @@
#import "file_view_controller.h"
@interface DocViewController ()
@end
@implementation DocViewController
#pragma mark -
#pragma mark View Life Cycle
- (void)viewDidLoad {
[super viewDidLoad];
}
#pragma mark -
#pragma mark Document Interaction Controller Delegate Methods
- (UIViewController *) documentInteractionControllerViewControllerForPreview: (UIDocumentInteractionController *) controller {
//return [[[[UIApplication sharedApplication]windows] firstObject]rootViewController];
return self;
}
- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller
{
[self removeFromParentViewController];
}
@end

View File

@@ -0,0 +1,8 @@
#ifndef IOS_LAUNCHER_H
#define IOS_LAUNCHER_H
#include <QString>
bool iosLaunchFile(QString file);
#endif // IOS_LAUNCHER_H

View File

@@ -0,0 +1,31 @@
#include "ios_launcher.h"
#include "file_view_controller.h"
#include <QString>
#import <UIKit/UIDocumentInteractionController.h>
bool iosLaunchFile(QString file)
{
NSString* url = file.toNSString();
NSURL* fileURL = [NSURL fileURLWithPath:url];
static DocViewController* mtv = nil;
if (mtv!=nil)
{
[mtv removeFromParentViewController];
[mtv release];
}
UIDocumentInteractionController* documentInteractionController = nil;
documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
UIViewController* rootv = [[[[UIApplication sharedApplication]windows] firstObject]rootViewController];
if (rootv!=nil)
{
mtv = [[DocViewController alloc] init];
[rootv addChildViewController:mtv];
documentInteractionController.delegate = mtv;
[documentInteractionController presentPreviewAnimated:NO];
return true;
}
return false;
}

Some files were not shown because too many files have changed in this diff Show More