django项目--在线播放

在线课堂

一、功能需求分析

1. 分析

在线直播,或点播设计到视频的存储,转码,加密,播放很多细节,个人单独开发不现实。本项目的在线课堂选择在线播放视频的形式。实际项目中一般选择云点播或者内嵌视频网站的方式进行。本项目选择是用百度云VOD点播来实现。

2. 功能

  • 视频展示页面

  • 视频播放详情

二、模型设计

1. 表字段分析

  • 老师表

    • 姓名

    • 职称

    • 简介

    • 头像

  • 课程分类表

    • 名称
  • 课程表

    • 课程名称

    • 封面

    • 视频地址

    • 时长

    • 简介

    • 大纲

    • 老师

    • 分类

2.模型定义

在course/models.py中定义如下模型

from django.db import models
​
from utils.models import BaseModel
​
​
class Teacher(BaseModel):
 name = models.CharField('讲师姓名', max_length=150, help_text='讲师姓名')
 title = models.CharField('职称', max_length=150, help_text='职称')
 profile = models.TextField('简介', help_text='简介')
 photo = models.URLField('头像url', default='', help_text='头像url')
​
 class Meta:
 db_table = 'tb_teachers'
 verbose_name = '讲师'
 verbose_name_plural = verbose_name
​
 def __str__(self):
 return self.name
​
​
class CourseCategory(BaseModel):
 name = models.CharField('课程分类名', max_length=100, help_text='课程分类名')
​
 class Meta:
 db_table = 'tb_course_category'
 verbose_name = '课程分类'
 verbose_name_plural = verbose_name
​
 def __str__(self):
 return self.name
​
​
class Course(BaseModel):
 title = models.CharField('课程名', max_length=150, help_text='课程名')
 cover_url = models.URLField('封面url', help_text='封面url')
 video_url = models.URLField('课程视频url', help_text='课程视频url')
 duration = models.DurationField('课程时长', help_text='课程时长')
 profile = models.TextField('课程简介', null=True, blank=True, help_text='课程简介')
 outline = models.TextField('课程大纲', null=True, blank=True, help_text='课程大纲')
 teacher = models.ForeignKey('Teacher', on_delete=models.SET_NULL, null=True, blank=True)
 category = models.ForeignKey('CourseCategory', on_delete=models.SET_NULL, null=True, blank=True)
​
 class Meta:
 db_table = 'tb_course'
 verbose_name = '课程'
 verbose_name_plural = verbose_name
​
 def __str__(self):
 return self.title

三、百度云VOD音视频点播

1. 开通百度VOD音频点播功能

  1. 注册后登陆首页,找到vod服务


开通即送55元包


2.添加媒资

官方使用说明

1563342650830.png
1563343001477.png
1563343108603.png

3.导入测试数据

# 在mysql数据库中添加自己的测试数据
INSERT INTO `tb_teachers`
(create_time, update_time, is_delete, name, title,`profile`,photo)
 VALUES
 ( '2019-07-17 14:26:05.000000', '2019-07-17 14:26:09.000000', '0', '心蓝', 'python高级讲师', 'python学院最帅的老师', '/media/xinlan.jpg');
​
# 导入课程分类数据
INSERT INTO `tb_course_category` VALUES ('1', '2019-07-17 14:34:33.000000', '2019-07-17 14:34:36.000000', '0', 'python基础');
INSERT INTO `tb_course_category` VALUES ('2', '2019-07-17 14:34:52.000000', '2019-07-17 14:34:55.000000', '0', 'python高级');
INSERT INTO `tb_course_category` VALUES ('3', '2019-07-17 14:35:20.000000', '2019-07-17 14:35:16.000000', '0', 'python框架');
​
# 导入课程数据
insert into tb_course (title, cover_url, video_url, duration, `profile`, outline, teacher_id, category_id, create_time, update_time, is_delete) values
('你的测试视频1名称', '你的测试视频缩略图URL', '你的测试视频URL', 212000000, '你的测试视频简介', '你的视频大纲', 1, 2, now(), now(), 0),
​
('你的测试视频2名称', '你的测试视频缩略图URL', '你的测试视频URL', '你的测试视频时长整数表示微秒', '你的测试视频简介', '你的视频大纲', 1, 2, now(), now(), 0);

四、视频展示列表

1.接口设计

  1. 接口说明:
类目 说明
请求方法 GET
url定义 /course/
参数格式 无参数
  1. 返回结果:

    文档下载页面

2.后端代码

  1. 视图
# 在course/views.py文件下创建如下视图
    from django.shortcuts import render, Http404
    from django.views import View
    ​
    from . import models
    ​
    ​
    def course_list(request):
     """
     在线课程列表
     url:/course/
     :param request:
     :return:
     """
     courses = models.Course.objects.only('title', 'cover_url', 'teacher__title', 'teacher__name').filter(
     is_delete=False).select_related(
     'teacher')
     return render(request, 'course/course.html', context={'courses': courses})
  1. 路由
# 在course/urs.py中定义如下路由
    from django.urls import path
    ​
    from . import views
    ​
    app_name = 'course'
    ​
    urlpatterns = [
     path('', views.course_list, name='index'),
    ]

3.前端代码

  1. html
<!-- 创建模板templates/course/course.html -->
    {% extends 'base/base.html' %}
    {% load static %}
    {% block title %}在线课堂{% endblock %}
    {% block link %}
     <link rel="stylesheet" href="{% static 'css/course/course.css' %}">
    {% endblock %}
    {% block main_start %}
     <main id="course-container">
     <div class="w1200">
     <ul class="course-list">
     {% for course in courses %}
     <li class="course-item">
     <a href="{% url 'course:course_detail' course.id %}">
     <img class="course-img" src="{{ course.cover_url }}"
     alt="{{ course.title }}">
     <div class="course-content">
     <p class="course-info">{{ course.title }}</p>
     <p class="course-author">{{ course.teacher.name}}({{ course.teacher.title }})</p>
     <p class="course-price free">免费</p>
     </div>
     </a>
     </li>
     {% endfor %}
     </ul>
     </div>
     </main>
    {% endblock %}
2.  css
        /* ==== 修改course.css如下 ====*/
        /* ========== top-wrap start =========  */
        #top-wrap {
         background: #fff;
         line-height: 60px;
         box-shadow: 0 4px 4px rgba(0,0,0,.1);
        }
        #top-wrap .top-title {
         float: left;
         font-size: 22px;
         margin-right: 140px;
        ​
        }
        #top-wrap .top-nav {
         display: flex;
         justify-content: space-between;
         width: 400px;
         color: #878787;
         font-size: 18px;
        }
        #top-wrap .top-nav li.active {
         color: #212121;
        }
        #top-wrap .top-nav  li:hover {
         text-shadow: 1px 1px 2px #212121;
        }
        /* ========== top-wrap end =========  */
        /* ========== course-container  start =========  */
        #course-container {
         flex: 1;
        }
        #course-container .course-list {
         display: flex;
         flex-flow: row wrap;
         margin-bottom: 20px;
        }
        #course-container .course-list .course-item {
         margin: 20px;
         width: 260px;
         height: 260px;
         background: #fff;
         position: relative;
         /* float: left; */
        }
        .course-list .course-item .course-img {
         width: 100%;
         height: 61.8%;
        }
        .course-list .course-item .course-content {
         padding: 0 20px;
         height: 150px;
         box-sizing: border-box;
        }
        .course-list .course-item:hover {
         box-shadow: 0 4px 8px rgba(0,0,0,.1);
        }
        .course-item .course-content .course-info {
         font-size: 16px;
         line-height: 1.5;
         max-height: 50px;
         overflow: hidden;
        }
        .course-item .course-content .course-author{
         color: #848383;
         font-size: 14px;
         line-height: 36px;
         overflow: hidden;
         text-overflow: ellipsis;
         white-space: nowrap;
        }
        .course-item .course-content .course-price {
         position: absolute;
         bottom: 10px;
         right: 21px;
         font-size: 16px;
         line-height: 30px;
         color: #6fa026;
        ​
        }
        .course-item .course-content .course-price span{
         font-size: 14px;
        }
        .course-item .course-content .course-price.free {
         color: green;
         float: right;
        }
        /* ========== course-container  end =========  */
        ​

五、视频播放详情页面

1.接口设计

  1. 接口说明:
类目 说明
请求方法 GET
url定义 /course/<int:course_id>/
参数格式 路径参数
  1. 参数说明:
参数名 类型 是否必须 描述
course_id 整数 视频id
  1. 返回结果:

    视频播放详情页面

2. 后端代码

  1. 视图
    # 在course/views.py文件下创建如下视图
    class CourseDetailView(View):
     """
     课程详情视图
     url:/course/<int:course_id>/
     """
    ​
     def get(self, request, course_id):
     course = models.Course.objects.only('title', 'cover_url', 'video_url', 'profile', 'outline', 'teacher__name',
     'teacher__photo', 'teacher__title', 'teacher__profile').select_related(
     'teacher').filter(is_delete=False, id=course_id).first()
    ​
     if course:
    ​
     return render(request, 'course/course_detail.html', context={'course': course})
     else:
     return Http404('此课程不存在')
    
  1. 路由
    # 在course/urs.py中添加如下路由
    path('<int:course_id>/', views.CourseDetailView.as_view(), name='course_detail')

3.前端代码

  1. html
    <!-- 创建模板templates/course/course_detail.html -->
{% extends 'base/base.html' %}
{% load static %}
{% block title %}视频详情-{{ course.title }}{% endblock %}
{% block link %}
    <link rel="stylesheet" href="{% static 'css/course/course-detail.css' %}">
{% endblock %}
{% block main_start %}
    <main id="main">
        <div class="w1200">
            <div class="course-contain">
                <div class="course-top-contain">
                    <h4 class="course-title">{{ course.title }}</h4>
                    <div class="course-other clearfix">
                        <div class="share">
                             <i></i>
                            <span>分享</span>
                            <div class="share-list">
                                    <div class="bshare-custom icon-medium"><div class="bsPromo bsPromo2"></div><a title="分享到QQ空间" class="bshare-qzone"></a><a title="分享到新浪微博" class="bshare-sinaminiblog"></a><a title="分享到QQ好友" class="bshare-qqim" href="javascript:void(0);"></a><a title="分享到微信" class="bshare-weixin" href="javascript:void(0);"></a><a title="更多平台" class="bshare-more bshare-more-icon more-style-addthis"></a><span class="BSHARE_COUNT bshare-share-count" style="float: none;">62.9K</span></div><script type="text/javascript" charset="utf-8" src="http://static.bshare.cn/b/buttonLite.js#style=-1&amp;uuid=&amp;pophcol=2&amp;lang=zh"></script><script type="text/javascript" charset="utf-8" src="http://static.bshare.cn/b/bshareC0.js"></script>
                            </div>
                        </div>
                        <div class="buy-list">
                            <span class="price">免费</span>
                        </div>
                    </div>
                </div>

                <div class="course-video" id="course-video">
                    <span class="course-data" style="display: none" data-video-url="{{ course.video_url }}"
                          data-cover-url="{{ course.cover_url }}"></span>
                </div>

                <div class="course-bottom-contain">
                    <div class="course-detail-list">
                        <div class="course-item clearfix">
                            <h5 class="course-title">{{ course.teacher.name }}</h5>
                            <div class="teacher-box clearfix">
                                <img src="{{ course.teacher.photo }}" alt="{{ course.teacher.name }}"
                                     title="{{ course.teacher.name }}" class="teacher-avatar">
                                <div class="teacher-info">
                                    <p class="teacher-name">{{ course.teacher.name }}</p>
                                    <p class="teacher-identify"> {{ course.teacher.title }}</p>
                                </div>
                            </div>
                            <div class="item-content">
                                {{ course.teacher.profile }}
                            </div>
                        </div>
                        <div class="course-item clearfix">
                            <h5 class="course-title">课程简介</h5>
                            <div class="item-content">
                                {{ course.profile }}
                            </div>
                        </div>
                        <div class="course-item clearfix">
                            <h5 class="course-title">课程大纲</h5>
                            <div class="item-content">
                                <p>{{ course.outline }}</p>
                            </div>
                        </div>
                        <div class="course-item clearfix">
                            <h5 class="course-title">帮助中心</h5>
                            <div class="item-content">
                                <p>1. 购买后的课程在线可反复观看学习,视频有效期以具体课程信息为准。 </p>
                                <p>2. 课程暂不支持下载观看,均为在线观看视频。 </p>
                                <p>3. 课程一经购买,不可转让、不可退款;仅限购买账号观看。</p>
                                <p>4. 如有问题请咨询客服: 400-1567-315 </p>
                            </div>
                        </div>
                        <div class="course-item clearfix">
                            <h5 class="course-title">关于潭州课堂</h5>
                            <div class="item-content">
                                湖南潭州教育网络科技有限公司拥有千余人的优秀师资团队,是一家师资丰富、教育产品类别众多的在线培训公司。公司总部座落于美丽的星城长沙,2015年9月正式入驻芯城科技园目前拥有近两万平米办公面积。
                                在潭州学习的学员已突破1000万人次在线学员覆盖全球,包括中国、加拿大、日本、美国、韩国等诸多国家。
                            </div>
                        </div>

                    </div>
                    <div class="course-side">
                        <h4 class="side-title">推荐课程</h4>
                    </div>
                </div>
            </div>
        </div>
    </main>

{% endblock %}
{% block script %}
    <script src="https://cdn.bdstatic.com/jwplayer/latest/cyberplayer.js"></script>
    <script src="{% static 'js/course/course_detail.js' %}"></script>
{% endblock %}


    ​
  1. js
    // 创建js文件 static/js/course/course.js
    $(function () {
      let $course_data = $(".course-data");
      let sVideoUrl = $course_data.data('video-url');
      let sCoverUrl = $course_data.data('cover-url');

      let player = cyberplayer("course-video").setup({
        width: '100%',
        height: 650,
        file: sVideoUrl,
        image: sCoverUrl,
        autostart: false,
        stretching: "uniform",
        repeat: false,
        volume: 100,
        controls: true,
        ak: '你自己的ak'
      });

    });
    
3.  css
        /*== 修改course_detail.css 代码如下===*/
        #main {
            flex: 1;
        }

        .course-contain {
            width: 100%;
        }

        .course-contain .course-top-contain, #course-video{
            width: 100%;
            background: #fff;
            /*padding:0 20px;*/
        }
        .course-top-contain .course-title {
            font-size: 30px;
            line-height: 2.5;
            margin: 0 20px;
        }

        .course-top-contain .course-other {
            line-height: 3.5;
            margin: 0 20px;
        }
        .course-other  .share {
            float: left;
        }
        .share span {
             margin-right: 8px;
        }
        .share i {
                display: inline-block;
            vertical-align: middle;
            width: 14px;
            height: 14px;
            margin-right: 5px;
            background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABM0lEQVQokY2Sr0tEQRSFv/dYsIiwYVFkg/deERZBbEaDiJpEBP8Ck6jYxCBuMlgNikGTIIjBIIhbBDGISUSwzIwW4wbtrmHnwWNxf5w099z7MXeYk1SrVbpJ1IrANjAKnAXvbgpdqaYegPF4Xha1hbSH2yQHZVrpCIpaCdj9p/WVxoFhUdsQtcVY94vaHhCAErAD/EboGTgoiNpYLAYiVAMmgU9gLnj3GP0ToAy8Bu8aBWArg6JmgbXg3VF+t+BdHahndQo0WvZvAFed3p6Bh8B3zvsB7kRtKj8oamOiNiFqCUAavHsHKsAmMAMMAtfAvahdiFpZ1NaBN+AFeBK1YtIuOaI2AhwD00AC9OXa+23/MXj3EbybB1ZbIIChrskJ3p0DtRb7stesLtEMQQU4Dd7d/gHKLE8gSrHhJgAAAABJRU5ErkJggg==);
        }
        .share .share-list{
            display: none;
            /*width: 130px;*/
            position: relative;
            background: #fff;
            text-align: center;
            line-height: 45px;
            left: 8px;
            /*box-shadow: 0 1px 2px #ccc;*/
            /*border: 1px solid #ddd;*/
            border-radius: 5px;
            z-index: 1;
        }
        .share:hover .share-list{
            display: inline-block;
        }
        .share-list:after{
            content: "";
            border: 12px solid transparent;
            border-right-color: #eee;
            position: absolute;
            top: 0px;
            left: -25px;
        }
        .share-list a{
            cursor: pointer;
        }
        .share-list a img{
            vertical-align: middle;
        }
        .course-other .buy-list {
            float: right;
        }

        .price {
            color: #f76363;
            float: right;
            margin-right: 20px;
            line-height: 40px;
            font-size: 20px;
        }

        .buy-btn {
            background-color: #ff8d3f;
            border: none;
            width: 120px;
            text-align: center;
            line-height: 40px;
            color: #fff;
            border-radius: 5px;
            font-size: 16px;
            float: right;
        }

        .course-video {
            border-top: 1px solid #ddd;
            height: 400px;
        }

        .course-bottom-contain {
            width: 100%;
            margin-top: 30px;
        }

        .curse-bottom-contain {
            margin-top: 30px;
            width: 100%;
        }

        .course-detail-list {
            width: 800px;
            float: left;
        }

        .course-detail-list .course-item {
            margin-bottom: 20px;
            background-color: #fff;
            padding: 20px;
        }

        .course-item .course-title{
            border-left: 6px solid #5b86db;
            padding: 0 10px;
            color: #202020;
            font-size: 18px;
        }

        .course-item .teacher-box {
            margin-top: 20px;
        }

        .teacher-box  .teacher-avatar {
            width: 62px;
            height: 62px;
            border-radius: 50%;
            float: left;
        }

        .teacher-box .teacher-info {
            float: left;
            margin-left: 10px;
            position: relative;
            font-size: 16px;
            color: #696969;
            height: 62px;
            width: 90%;
        }

        .teacher-name {
            position: absolute;
            left: 0;
            top: 8px;
        }
        .teacher-identify {
            position: absolute;
            bottom: 8px;
            left: 0;
        }

        .item-content {
            font-size: 14px;
            color: #888;
            line-height: 2em;
            margin-top: 20px;
        }

        .item-course-title {
            font-size: 18px;
            color: #202020;
            float: left;
            line-height: 40px;
        }

        .item-buy-list {
            float: right;
            margin-top: 0;
        }

        .course-side {
            float: right;
            width: 360px;
            background-color: #fff;
            padding: 20px;
            padding-bottom: 0;
            box-sizing: border-box;
        }
        .course-side  .side-title {
            font-size: 18px;
            line-height: 2.4;
        }

       
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容