需要全部代码请点赞关注收藏后评论区留言私信~~~
与传统的影视行业相比,诞生于移动互联网时代的短视频是个全新行业,它制作方便又容易传播,一出现就成为大街小巷的时髦潮流。 各行各业的人们均可通过短视频展示自己,短小精悍的视频片段原来能够容纳如此丰富的内蕴。许多人依靠短视频获得大量关注,乃至成为谋生手段,本人甚至成为网红。 这些都离不开短视频分享平台的推波助澜,尤其是一对欢喜冤家——抖音和快手,它们的使用频率甚至超过了老牌App。其中抖音的国际版TikTok更是风靡全球,它鼓励国外年轻人勇敢表现自我,成为中国企业的出海标杆。
短视频分享平台需要满足两种角色的使用:
(1)一种是作为内容创作者发布自己的视频,
(2)另一种是作为用户欣赏平台上的已有视频。
还有一些点赞关注弹幕功能这里就不再赘述了 相信大家对短视频平台都不陌生
短视频分享与传统的图文界面大相径庭,因为单个短视频就已经填满了整块屏幕,势必要求采取专门的方式浏览短视频,从用户界面到后台服务,短视频分享主要集成了以下App技术
(1)二代翻页视图:向上滑动浏览界面能够拉出新视频。
(2)下拉刷新控件:下拉手势会触发刷新动作获取最新发布的视频。
(3)新型播放器:ExoPlayer能够快速响应网络视频。
(4)相机视图:录制短视频需要自定义相机视图。
(5)媒体检索工具:从视频中截取某帧作为视频封面。
(6)循环视图:备选的视频各帧画面从左往右依次排开。
(7)网络通信框架:上传视频信息与获取视频列表均需与后端交互。
(8)移动数据格式JSON:通过JSON结构封装http交互数据。
下面介绍代码模块中各部分之间的关系
(1)ShortViewActivity.java:这是短视频分享的浏览首页。
(2)VideoFragment.java:这是每个短视频的碎片页。
(3)ShortTakeActivity.java:这是视频录制页面。
(4)ShortEditActivity.java:这是视频信息的编辑页面。
(5)ShortCoverActivity.java:这是视频封面的挑选页面。
(6)VideoLoadTask.java:这是视频列表的加载任务。
在初始界面可以点击录制视频 录制完之后进入编辑页面,可以输入视频的名称 摘要 描述 日期等等信息 发布之后可以自己观看(此处要连接后台服务器IDEA)



部分代码如下 需要全部代码请点赞关注收藏后评论区留言私信~~~
短视频分享首页代码
package com.example.video;
import android.content.Intent;
import android.os.Bundle;
import android.view.WindowManager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager2.widget.ViewPager2;
import com.example.video.adapter.VideoPagerAdapter;
import com.example.video.bean.VideoInfo;
import com.example.video.task.VideoLoadTask;
import java.util.ArrayList;
import java.util.List;
public class ShortViewActivity extends AppCompatActivity {
private SwipeRefreshLayout srl_dynamic; // 声明一个下拉刷新布局对象
private List<VideoInfo> mVideoList = new ArrayList<>(); // 声明一个地址列表
private VideoPagerAdapter mAdapter; // 声明一个视频翻页适配器对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_short_view);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕常亮
initView(); // 初始化视图
startLoad(); // 开始加载短视频
}
// 初始化视图
private void initView() {
srl_dynamic = findViewById(R.id.srl_dynamic);
ViewPager2 vp2_content = findViewById(R.id.vp2_content);
srl_dynamic.setOnRefreshListener(() -> startLoad()); // 设置下拉布局的下拉刷新监听器
// 构建一个视频地址的翻页适配器
mAdapter = new VideoPagerAdapter(this, mVideoList);
vp2_content.setAdapter(mAdapter); // 设置二代翻页视图的适配器
findViewById(R.id.iv_add).setOnClickListener(v ->
startActivity(new Intent(this, ShortTakeActivity.class)));
}
// 开始加载最新的短视频
private void startLoad() {
// 创建一个视频列表加载任务
VideoLoadTask task = new VideoLoadTask(this, videoList -> {
srl_dynamic.setRefreshing(false); // 结束下拉刷新布局的刷新动作
mVideoList.clear();
mVideoList.addAll(videoList);
mAdapter.notifyDataSetChanged(); // 通知适配器数据发生变化
});
task.start(); // 启动短视频列表加载任务
}
}
短视频任务类代码
package com.example.video;
import android.content.Intent;
import android.hardware.camera2.CameraCharacteristics;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;
import androidx.appcompat.app.AppCompatActivity;
import com.example.video.util.MediaUtil;
import com.example.video.util.Utils;
import com.example.video.widget.ArcView;
import com.example.video.widget.Camera2View;
public class ShortTakeActivity extends AppCompatActivity {
private final static String TAG = "ShortTakeActivity";
private RelativeLayout rl_preview; // 声明一个相对布局对象
private VideoView vv_content; // 声明一个视频视图对象
private RelativeLayout rl_record; // 声明一个相对布局对象
private Camera2View c2v_preview; // 声明一个二代相机视图对象
private TextView tv_cost; // 声明一个文本视图对象
private ImageView iv_record; // 声明一个图像视图对象
private ArcView av_progress; // 声明一个圆弧视图对象
private LinearLayout ll_next; // 声明一个线性布局对象
private int mCameraType = CameraCharacteristics.LENS_FACING_BACK; // 默认使用后置摄像头拍照
private boolean isRecording = false; // 是否正在录像
private int MAX_RECORD_TIME = 15; // 最大录制时长,默认15秒
private String mVideoPath; // 待上传的视频文件路径
private int CHOOSE_CODE = 3; // 只在视频库挑选视频的请求码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_short_take);
initView(); // 初始化视图
initCamera(); // 初始化相机
}
// 初始化视图
private void initView() {
findViewById(R.id.iv_back).setOnClickListener(v -> finish());
rl_preview = findViewById(R.id.rl_preview);
c2v_preview = findViewById(R.id.c2v_preview);
vv_content = findViewById(R.id.vv_content);
rl_record = findViewById(R.id.rl_record);
tv_cost = findViewById(R.id.tv_cost);
iv_record = findViewById(R.id.iv_record);
av_progress = findViewById(R.id.av_progress);
iv_record.setOnClickListener((v) -> dealRecord()); // 处理录像动作
findViewById(R.id.btn_album).setOnClickListener(v -> {
// 创建一个内容获取动作的意图(准备跳到系统视频库)
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); // 是否允许多选
intent.setType("video/*"); // 类型为视频
startActivityForResult(intent, CHOOSE_CODE); // 打开系统视频库
});
findViewById(R.id.iv_switch).setOnClickListener((v) -> {
mCameraType = (mCameraType == CameraCharacteristics.LENS_FACING_BACK) ?
CameraCharacteristics.LENS_FACING_FRONT : CameraCharacteristics.LENS_FACING_BACK;
c2v_preview.switchCamera(mCameraType); // 切换摄像头
});
ll_next = findViewById(R.id.ll_next);
findViewById(R.id.btn_cancel).setOnClickListener(v -> finish());
findViewById(R.id.btn_next).setOnClickListener((v) -> {
// 下面跳到视频编辑界面
Intent intent = new Intent(this, ShortEditActivity.class);
intent.putExtra("video_path", mVideoPath);
startActivity(intent);
});
}
// 处理录像动作
private void dealRecord() {
if (!isRecording) {
iv_record.setImageResource(R.drawable.short_stop);
c2v_preview.startRecord(MAX_RECORD_TIME, false); // 开始录像
startRecordAnim(); // 播放录像动画
isRecording = !isRecording;
} else {
rl_record.setVisibility(View.GONE);
c2v_preview.stopRecord(); // 停止录像
}
}
// 初始化相机
private void initCamera() {
// 初始化二代相机,并指定准备就绪监听器和停止录像监听器
c2v_preview.initCamera(mCameraType, (size) -> {
int adjustHeight = Utils.getScreenWidth(this) * size.getWidth() / size.getHeight();
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) c2v_preview.getLayoutParams();
params.height = adjustHeight; // 根据预览尺寸调整预览窗口的高度
c2v_preview.setLayoutParams(params); // 设置预览视图的布局参数
}, (result) -> {
runOnUiThread(() -> {
isRecording = false;
Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
mVideoPath = c2v_preview.getVideoPath();
playVideo(); // 播放视频
});
});
}
// 播放视频
private void playVideo() {
c2v_preview.closeCamera(); // 关闭相机
rl_preview.setVisibility(View.GONE);
rl_record.setVisibility(View.GONE);
vv_content.setVisibility(View.VISIBLE);
vv_content.setVideoURI(Uri.parse(mVideoPath)); // 设置视频视图的视频路径
// 设置视频视图的就绪监听器,在播放源准备完成后,命令媒体播放器循环播放视频
vv_content.setOnPreparedListener(mp -> mp.setLooping(true));
vv_content.start(); // 视频视图开始播放
ll_next.setVisibility(View.VISIBLE);
}
// 播放录像动画
private void startRecordAnim() {
tv_cost.setVisibility(View.VISIBLE);
av_progress.setVisibility(View.VISIBLE);
// 定义一个圆弧渐进动画
Animation animation = new Animation() {
private String costDesc=""; // 耗时描述
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
String cost = String.format("%.1f秒", MAX_RECORD_TIME*interpolatedTime);
if (!costDesc.equals(cost)) { // 秒数发生变化
costDesc = cost;
tv_cost.setText(costDesc);
av_progress.setAngle((int) (360*interpolatedTime)); // 设置圆弧的角度
}
}
};
animation.setDuration(MAX_RECORD_TIME*1000); // 设置动画的持续时间
tv_cost.startAnimation(animation); // 开始播放动画
}
@Override
protected void onDestroy() {
super.onDestroy();
c2v_preview.closeCamera(); // 关闭相机
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode==RESULT_OK && requestCode==CHOOSE_CODE) { // 从视频库回来
if (intent.getData() != null) { // 选择一个视频
Uri uri = intent.getData(); // 获得已选择视频的路径对象
// 把指定Uri的视频复制一份到内部存储空间,并返回存储路径
mVideoPath = MediaUtil.copyVideoFromUri(this, uri);
playVideo(); // 播放视频
}
}
}
private int mCurrentPosition = 0; // 当前的播放位置
@Override
protected void onResume() {
super.onResume();
// 恢复页面时立即从上次断点开始播放视频
if (!TextUtils.isEmpty(mVideoPath) && mCurrentPosition>0 && !vv_content.isPlaying()) {
vv_content.seekTo(mCurrentPosition); // 找到指定位置
vv_content.start(); // 视频视图开始播放
}
}
@Override
protected void onPause() {
super.onPause();
// 暂停页面时保存当前的播放进度
if (!TextUtils.isEmpty(mVideoPath) && vv_content.isPlaying()) { // 视频视图正在播放
// 获得视频视图当前的播放位置
mCurrentPosition = vv_content.getCurrentPosition();
vv_content.pause(); // 视频视图暂停播放
}
}
}
创作不易 觉得有帮助请点赞关注收藏~~~
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie
当我在我的Rails应用程序根目录中运行rakedoc:app时,API文档是使用/doc/README_FOR_APP作为主页生成的。我想向该文件添加.rdoc扩展名,以便它在GitHub上正确呈现。更好的是,我想将它移动到应用程序根目录(/README.rdoc)。有没有办法通过修改包含的rake/rdoctask任务在我的Rakefile中执行此操作?是否有某个地方可以查找可以修改的主页文件的名称?还是我必须编写一个新的Rake任务?额外的问题:Rails应用程序的两个单独文件/README和/doc/README_FOR_APP背后的逻辑是什么?为什么不只有一个?
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
我正在使用Postgres.app在OSX(10.8.3)上。我已经修改了我的PATH,以便应用程序的bin文件夹位于所有其他文件夹之前。Rammy:~phrogz$whichpg_config/Applications/Postgres.app/Contents/MacOS/bin/pg_config我已经安装了rvm并且可以毫无错误地安装pggem,但是当我需要它时我得到一个错误:Rammy:~phrogz$gem-v1.8.25Rammy:~phrogz$geminstallpgFetching:pg-0.15.1.gem(100%)Buildingnativeextension
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195