python-Django项目实战-播放与下载

歌曲播放与下载

从网站首页、歌曲排行榜和歌曲搜索得知,每个页面的歌曲信息都设置了歌曲播放的地址链接,只要在页面上单击歌曲名即可访问歌曲播放页。总的来说,歌曲播放页是音乐网站的核心页面,所有页面的歌曲信息都有播放链接,通过播放链接而已访问可以访问歌曲播放页。

定义路由

歌曲播放由项目应用play实现,在play的urls.py中分别定义路由play和download。

路由的定义过程如下:

python 复制代码
from django.urls import path
from .views import *
urlpatterns = [
    # 歌曲播放页
    path('<int:id>.html', playView, name='play'),
    # 歌曲下载
    path('download/<int:id>.html', downloadView, name='download')
]

视图定义

路由play和download设置路由变量id,该变量是模型Song的主键id,主要用于标记和区分当前播放的歌曲信息。路由play为用户提供在线试听、歌曲下载、歌曲点评链接和相关歌曲推荐,路由download用于实现歌曲下载功能。在play的view.py中分别定义视图函数playView和downloadView。

代码如下:

python 复制代码
from django.shortcuts import render
from django.http import StreamingHttpResponse
from index.models import *
​
def playView(request, id):
    # 热搜歌曲
    searchs = Dynamic.objects.select_related('song').order_by('-search').all()[:6]
    # 相关歌曲推荐
    type = Song.objects.values('type').get(id=id)['type']
    relevant = Dynamic.objects.select_related('song').filter(song__type=type).order_by('-plays').all()[:6]
    # 歌曲信息
    songs = Song.objects.get(id=int(id))
    # 播放列表
    play_list = request.session.get('play_list', [])
    exist = False
    if play_list:
        for i in play_list:
            if int(id) == i['id']:
                exist = True
    if exist == False:
        play_list.append({'id': int(id), 'singer': songs.singer, 'name': songs.name, 'time': songs.time})
    request.session['play_list'] = play_list
    # 歌词
    if songs.lyrics != '暂无歌词':
        lyrics = str(songs.lyrics.url)[1::]
        with open(lyrics, 'r', encoding='utf-8') as f:
            lyrics = f.read()
    # 添加播放次数
    # 功能扩展:可使用Session实现每天只添加一次播放次数
    p = Dynamic.objects.filter(song_id=int(id)).first()
    plays = p.plays + 1 if p else 1
    Dynamic.objects.update_or_create(song_id=id, defaults={'plays': plays})
    return render(request, 'play.html', locals())
​
​
def downloadView(request, id):
    # 添加下载次数
    p = Dynamic.objects.filter(song_id=int(id)).first()
    download = p.download + 1 if p else 1
    Dynamic.objects.update_or_create(song_id=id, defaults={'download': download})
    # 读取文件内容
    # 根据id查找歌曲信息
    songs = Song.objects.get(id=int(id))
    file = songs.file.url[1::]
    def file_iterator(file, chunk_size=512):
        with open(file, 'rb') as f:
            while True:
                c = f.read(chunk_size)
                if c:
                    yield c
                else:
                    break
    # 将文件内容写入StreamingHttpResponse对象
    # 并以字节流方式返回给用户,实现文件下载
    f = str(id) + '.m4a'
    response = StreamingHttpResponse(file_iterator(file))
    response['Content-Type'] = 'application/octet-stream'
    response['Content-Disposition'] = 'attachment; filename="%s"' %(f)
    return response

视图函数playView分别实现4次数据查询、播放列表的设置、歌词的读取和播放次数的累加,功能的实现过程说明如下:

(1)变量searchs实现歌曲搜索框下方的热搜歌曲,变量relevant实现相关歌曲推荐功能,将同一类型的歌曲展示在歌曲播放页的最下方。

(2)播放列表由会话Session存储当前用户的播放记录。

(3)变量songs获取当前歌曲的信息,如果当前歌曲存在歌词文件,就读取歌词文件的数据内容,并以变量lyrics表示。

(4)累加播放次数用于查询模型Dynamic是否存在歌曲动态信息,若存在,则将播放次数累加1,否则新增动态信息并将播放次数设为1,最后调用内置方法update_or_create实现动态信息的更新或新增操作。如果模型Dynamic不存在当前歌曲的动态信息,那么内置方法update_or_create将执行数据新增操作,否则执行数据更新操作。

视图函数downloadView实现歌曲文件的下载功能,歌曲每下载一次,就对歌曲的下载次数累加1,因此,视图函数downloadView实现两个功能;累计下载次数和文件下载,功能说明如下:

(1)累加下载次数与累加播放次数的功能相似,两者都是调用Django内置方法update_or_create实现动态信息的更新或新增操作,前者是操作模型字段download,后者是操作模型字段plays。

(2)文件下载使用StreamingHttpResponse实现,这是实现流式响应输出(流式响应输出是使用Python的迭代器将数据进行分段处理并传输)。

模板定义

核心就是音乐的播放部分

bash 复制代码
<audio id="myAudio" controls autoplay>
  <source src="{{ song.file.url }}" type="audio/mpeg">
</audio>

下载可以通过a标签

ini 复制代码
<a href="/user/test/xxxx.txt" download="文件名.txt">点击下载</a>
​
<button class="down">下载</button>

但是有个情况,比如txt、png、jpg等这些浏览器支持直接打开的文件是不会执行下载任务的,而是会直接打开文件,这个时候就需要给a标签添加一个属性"download"。

代码借鉴

xml 复制代码
{% extends "base.html"  %}
{% load static %}
​
{% block link  %}
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
<link rel="stylesheet" href="{% static "css/common.css" %}">
<link rel="stylesheet" href="{% static "css/play.css" %}">
{% endblock %}
​
{# ① #}
{% block body %}
<body>
    <div class="header">
        <a href="/" class="logo"><img src="{% static "image/logo.png" %}"></a>
        <div class="search-box">
            <!-- 歌曲搜索框 -->
            <form id="searchForm" action="{% url 'search' 1 %}" method="post">
            {% csrf_token %}
            <div class="search-keyword">
              <input id="kword" name="kword" type="text" class="keyword" maxlength="120">
            </div>
            <input id="subSerch" type="submit" class="search-button" value="搜 索" />
            </form>
            <div id="suggest" class="search-suggest"></div>
            <div class="search-hot-words">
                {% for s in searchs %}
                <a target="play" href="{% url 'play' s.song.id %}" >{{ s.song.name }}</a>
                {% endfor  %}
            </div>
        </div>
    </div><!--end header-->
    <div class="nav-box">
        <div class="nav-box-inner">
            <ul class="nav clearfix">
                <li><a href="{% url 'index' %}">首页</a></li>
                <li><a href="{% url 'ranking' %}" target="_blank">歌曲排行</a></li>
                <li><a href="{% url 'home' 1 %}" target="_blank">用户中心</a></li>
            </ul>
        </div>
    </div><!--end nav-box-->
    <div class="wrapper clearfix">
        <div class="content">
        
            {# ② #}
            <div class="product-detail-box clearfix">
                <div class="product-pics">
                    <div class="music_box">
                        <div id="jquery_jplayer_1" class="jp-jplayer" data-url={{ songs.file.url }}></div>
                        <div class="jp_img layz_load pic_po" title="点击播放"><img data-src={{ songs.img.url }}></div>
                        <div id="jp_container_1" class="jp-audio">
                            <div class="jp-gui jp-interface">
                                <div class="jp-time-holder clearfix">
                                    <div class="jp-progress">
                                        <div class="jp-seek-bar">
                                            <div class="jp-play-bar"></div>
                                        </div>
                                    </div>
                                    <div class="jp-time">
                                        <span class="jp-current-time"></span> /
                                        <span class="jp-duration"></span>
                                    </div>
                                </div>
                                <div class="song_error_corr" id="songCorr">
                                    <b class="err_btn">纠错</b>
                                    <ul>
                                        <li><span>歌词文本错误</span></li>
                                        <li><span>歌词时间错误</span></li>
                                        <li><span>歌曲错误</span></li>
                                    </ul>
                                </div>
                                <div class="jp-volume-bar">
                                    <div class="jp-volume-bar-value"></div>
                                </div>
                                <ul class="jp-controls clearfix">
                                    <li>
                                        <a class="jp-play" tabindex="1" title="play"></a>
                                        <a class="jp-pause" tabindex="1" title="pause"></a>
                                    </li>
                                    <li>
                                        <a class="jp-stop" tabindex="1" title="stop"></a>
                                    </li>
                                    <li>
                                        <a class="jp-repeat" tabindex="1" title="repeat"></a>
                                        <a class="jp-repeat-off" tabindex="1" title="repeat off"></a>
                                    </li>
                                    <li class="sound">
                                        <a class="jp-mute" tabindex="1" title="mute"></a>
                                        <a class="jp-unmute" tabindex="1" title="unmute"></a>
                                    </li>
                                </ul>
                            </div>
                        </div>
                        <div class="jplayer_content">
                            <ul id="lrc_list" class="lrc_list"></ul>
                        </div>
                    </div><!--end music_box-->
                    <textarea id="lrc_content" style="display: none;">
                      {{ lyrics }}
                    </textarea>
                </div><!--end product-pics-->
                <div class="product-detail-main">
                    <div class="product-price">
                        <h1 id="currentSong" >{{ songs.name }}</h1>
                        <div class="product-price-info">
                            <span>歌手:{{ songs.singer }}</span>
                        </div>
                        <div class="product-price-info">
                            <span>专辑:{{ songs.album }}</span>
                            <span>语种:{{ songs.languages }}</span>
                        </div>
                        <div class="product-price-info">
                            <span>流派:{{ songs.type }}</span>
                            <span>发行时间:{{ songs.release }}</span>
                        </div>
                    </div><!--end product-price-->
                    <div class="product-comment">
                        <div class="links clearfix">
                            <a class="minimum-link-A click_down" href="{% url 'download' songs.id %}">下载</a>
                            <a class="minimum-link-A" href="{% url 'comment' songs.id %}" >歌曲点评</a>
                        </div><!-- end links-->
                        {# ③ #}
                        <h3 class="list_title">当前播放列表</h3>
                        <ul class="playing-li" id="songlist">
                            <!--播放列表-->
                            {% for item in play_list %}
                            {%if item.id == songs.id %}
                            <li data-id="{{item.id}}" class="current">
                            {%else %}
                            <li data-id="{{item.id}}">
                            {%endif %}
                            <span class="num">{{forloop.counter}}</span>
                            <a class="name" href="{% url 'play' item.id %}" target="play" >{{item.name}}</a>
                            <a class="singer" href="javascript:;" target="_blank" >{{item.singer}}</a>
                            </li>
                            {% endfor %}
                        </ul>
                        <div class="nplayL-btns" id="playleixin">
                        <ul>
                            <li class="order current" data-run="order">
                                <a class="icon" href="javascript:void(0)" title="顺序播放"></a>
                            </li>
                            <li class="single" data-run="single">
                                <a class="icon" title="单曲循环" href="javascript:void(0)"></a>
                            </li>
                            <li class="random" data-run="random">
                                <a class="icon" title="随机播放" href="javascript:void(0)"></a>
                            </li>
                            <li class="next" data-run="next">
                                <a href="javascript:void(0)"><i></i>播放下一首</a>
                            </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
​
            <div class="section">
                <div class="section-header">
                    <h3>相关歌曲</h3>
                </div>
                <div class="section-content">
                    <div class="parts-box">
                        <a href="javascript:;" target="_self" id="J_PartsPrev" class="prev-btn"><i></i></a>
                        <div class="parts-slider" id="J_PartsList">
                            <div class="parts-list-wrap f_w">
                                <ul id="" class="parts-list clearfix f_s">
                                    {% for item in relevant %}
                                    <li>
                                        {% if item.song.id != songs.id %}
                                        <a class="pic layz_load pic_po" href="{% url 'play' item.song.id %}" target="play" >
                                            <img data-src="{{ item.song.img.url }}">
                                        </a>
                                        <h4><a href="{% url 'play' item.song.id %}" target="play" >{{ item.song.name}}</a></h4>
                                        <a href="javascript:;" class="J_MoreParts accessories-more">{{ item.song.singer }}</a>
                                        {% endif %}
                                    </li>
                                    {% endfor %}
                                </ul>
                            </div>
                        </div>
                        <a href="javascript:;" target="_self" id="J_PartsNext" class="next-btn"><i></i></a>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script data-main="{% static "js/play.js" %}" src="{% static "js/require.js" %}"></script>
</body>
{% endblock  %}

我们将模板文件play.html实现的功能划分为3部分,在上述代码中已标注。

(1)标注①与之前的功能相同,不再重复讲解

(2)标注②实现了歌曲播放、歌曲信息、歌曲下载与歌曲点评。歌曲播放功能在class="jp-jplayer"的div标签里实现,标签属性data-url设置歌曲文件的路径地址,由JavaScript播放歌曲文件;歌曲信息在class="product-detail-main"的div标签里实现,而歌词动态效果在textarea文本框实现,歌曲的播放进度与歌词的滑动效果由JavaScript实现,歌曲下载与歌曲点评在class="links clearfix"的div标签中分别设置路由download和comment。

(3)标注③实现歌曲的播放列表和相关歌曲列表。歌曲的播放列表遍历模板上下文play_list生成数据列表,相关歌曲列表遍历模板上下文relevant生成相关歌曲列表。

歌曲点评

在歌曲播放页设置了歌曲点评的地址链接,单击"点评"按钮即可访问当前歌曲的点评页面。歌曲点评页实现两个功能:歌曲点评和歌曲点评信息列表,说明如下:

(1)歌曲点评是为用户提供歌曲点评功能,以表单的形式实现数据提交。

(2)歌曲点评信息列表是根据当前歌曲信息查找模型Comment的点评数据,并以数据列表的形式展示在网页。

定义路由

我们在项目应用comment中实现歌曲点评页,打开comment的urls.py定义歌曲点评的路由信息,代码如下:

javascript 复制代码
from django.urls import path
from .views import *
urlpatterns = [
    path('<int:id>.html', commentView, name='comment'),
]

定义视图

路由comment设置路由变量id,他代表模型Song的主键id,用于区分和识别当前歌曲的点评信息。路由的HTTP请求由视图函数commentView负责接收和处理,在comment的views.py中定义视图函数commentView,代码如下:

python 复制代码
from django.core.paginator import Paginator
from django.core.paginator import EmptyPage
from django.core.paginator import PageNotAnInteger
from django.shortcuts import render, redirect
from django.shortcuts import reverse
from django.http import Http404
from index.models import *
import time
​
​
def commentView(request, id):
    # 热搜歌曲
    searchs = Dynamic.objects.select_related('song').order_by('-search').all()[:6]
    # 点评内容的提交功能
    if request.method == 'POST':
        text = request.POST.get('comment', '')
        # 如果用户处于登录状态,则使用用户名,反之使用匿名用户
        if request.user.username:
            user = request.user.username
        else:
            user = '匿名用户'
        now = time.strftime('%Y-%m-%d', time.localtime(time.time()))
        if text:
            comment = Comment()
            comment.text = text
            comment.user = user
            comment.date = now
            comment.song_id = id
            comment.save()
        return redirect(reverse('comment', kwargs={'id': str(id)}))
    else:
        songs = Song.objects.filter(id=id).first()
        # 歌曲不存在抛出404异常
        if not songs:
            raise Http404('歌曲不存在')
        c = Comment.objects.filter(song_id=id).order_by('date')
        page = int(request.GET.get('page', 1))
        paginator = Paginator(c, 2)
        try:
            pages = paginator.page(page)
        except PageNotAnInteger:
            pages = paginator.page(1)
        except EmptyPage:
            pages = paginator.page(paginator.num_pages)
        return render(request, 'comment.html', locals())

视图函数commentView分别对GET请求和POST请求执行不同的处理,并且将路由变量id作为函数参数id。当我们从歌曲播放页进入歌曲点评页,浏览器访问歌曲点评页,相当于向网站发送GET请求,视图函数commentView执行以下处理:

(1)视图函数commentView将函数参数id作为模型Song的查询条件,如果模型Song没有记录当前歌曲,就抛出404异常并提示歌曲不存在。

(2)如果模型Song存在当前歌曲,就将函数参数id作为模型Comment的查询条件,查询当前歌曲的点评信息,查询结果命名为c。然后从GET请求中获取请求参数page,并且生成变量page,如果请求参数page不存在,变量page的值就设为1。

(3)最后将变量page和查询结果c进行分页处理,生成分页对象pages,并调用模板文件comment.html生成歌曲点评页。

如果我们在歌曲点评页填写点评内容并单击"发布"按钮,浏览器就向网站发送POST请求,视图函数commentView将会接收一个POST请求,并执行以下处理:

(1)从网页表单中获取点评内容,并将点评内容命名为变量text。然后获取当前用户名,如果当前用户处于未登录状态,那么用户名设为匿名用户,并且以变量user表示。

(2)如果变量text不为空,就在模型Comment中新增一条点评信息,分别记录点评内容、用户名和点评日期,模型Comment的外键字段song设为函数参数id,这是为当前歌曲新增一条点评信息。

(3)最后重定向歌曲点评页,以GET请求方式访问歌曲点评页,将新增的点评信息显示在歌曲点评页,网站的重定向可以防止表单多次提交,解决同一条点评信息重复创建的问题。

定义模型

下一步在模板文件comment.html中编写相应的网页内容。实现5个功能:歌曲搜索框、网站导航栏功能、歌曲点评框、歌曲点评信息列表和分页导航功能。

xml 复制代码
{% extends "base.html"  %}
{% load static %}
​
{% block link %}
<link rel="shortcut icon" href="{% static "favicon.ico" %}">
<link rel="stylesheet" href="{% static "css/common.css" %}">
<link rel="stylesheet" href="{% static "css/comment.css" %}">
{% endblock %}
​
{% block body  %}
<body class="review">
<div class="header">
<a href="/" class="logo"><img src="{% static "image/logo.png" %}"></a>
<div class="search-box">
    <form id="searchForm" action="{% url 'search' 1 %}" method="post">
        {% csrf_token %}
        <div class="search-keyword">
            <input id="kword" name="kword" type="text" class="keyword" maxlength="120">
        </div>
        <input id="subSerch" type="submit" class="search-button" value="搜 索"/>
    </form>
    <div id="suggest" class="search-suggest"></div>
    <div class="search-hot-words">
        {% for s in searchs %}
            <a target="play" href="{% url 'play' s.song.id %}" >{{ s.song.name }}</a>
        {% endfor  %}
    </div>
</div>
</div><!--end header-->
<div class="nav-box">
<div class="nav-box-inner">
    <ul class="nav clearfix">
        <li><a href="{% url 'index' %}">首页</a></li>
        <li><a href="{% url 'ranking' %}" target="_blank">歌曲排行</a></li>
        <li><a href="{% url 'home' 1 %}" target="_blank">用户中心</a></li>
    </ul>
</div>
</div><!--end nav-box-->
<div class="wrapper">
<div class="breadcrumb">
    <a href="/">首页</a> &gt;
            <a href="{% url 'play' id %}" target="_self">{{songs.name}}</a> &gt;
    <span>点评</span>
</div>
<div class="page-title" id="currentSong"></div>
</div>
<div class="wrapper">
<div class="section">
    <div class="section-header"><h3 class="section-title">网友点评</h3></div>
    <div class="section-content comments-score-new review-comments-score clearfix">
        <div class="clearfix">
​
        <!--点评框-->
            <div class="comments-box">
            <div class="comments-box-title">我要点评<<{{ songs.name }}>></div>
            <div class="comments-default-score clearfix"></div>
            <form action="" method="post" id="usrform">
                {% csrf_token %}
                <div class="writebox">
                    <textarea name="comment" form="usrform"></textarea>
                </div>
                <div class="comments-box-button clearfix">
                <input type="submit" value="发布" class="_j_cc_post_entry cc-post-entry" id="scoreBtn">
                <div data-role="user-login" class="_j_cc_post_login"></div>
                </div>
                <div id="scoreTips2" style="padding-top:10px;"></div>
            </form>
            </div>
        </div>
    </div>
</div>
</div>
​
​
<div class="wrapper clearfix">
<div class="content">
<div id="J_CommentList">
<ul class="comment-list">
    {% for item in pages.object_list  %}
    <li class="comment-item ">
        <div class="comments-user">
        <span class="face">
        <img src="{% static "image/user.jpg" %}" width="60" height="60">
        </span>
        </div>
        <div class="comments-list-content">
        <div class="single-score clearfix">
            <span class="date">{{ item.date }}</span>
            <div><span class="score">{{ item.user }}</span></div>
        </div>
        <!--comments-content-->
        <div class="comments-content">
            <div class="J_CommentContent comment-height-limit">
                <div class="content-inner">
                    <div class="comments-words">
                        <p>{{ item.text }}</p>
                    </div>
                </div>
            </div>
        </div>
        </div>
    </li>
    {% endfor  %}
</ul>
<div class="page-box">
    <div class="pagebar" id="pageBar">
        {% if pages.has_previous %}
            <a href="{% url 'comment' id %}?page={{ pages.previous_page_number }}" class="prev" target="_self"><i></i>上一页</a>
        {% endif %}
        {% for page in pages.paginator.page_range %}
            {% if pages.number == page %}
                <span class="sel">{{ page }}</span>
            {% else %}
                <a href="{% url 'comment' id %}?page={{ page }}" target="_self">{{ page }}</a>
            {% endif %}
        {% endfor %}
        {% if pages.has_next %}
            <a href="{% url 'comment' id %}?page={{ pages.next_page_number }}" class="next" target="_self"><i></i>下一页</a>
        {% endif %}
    </div>
</div>
</div>
</div>
</div>
</body>
{% endblock  %}
相关推荐
Python测试之道34 分钟前
测试开发面试题:Python高级特性通俗讲解与实战解析
开发语言·python
低代码布道师3 小时前
第五部分:阶段项目 4:构建 RESTful API 服务器
服务器·后端·restful
这里是小悦同学呀!4 小时前
python学习day2
java·python·学习
菠萝崽.5 小时前
RabbitMQ高级篇-MQ的可靠性
java·分布式·后端·消息队列·rabbitmq·异步编程
未来可期叶8 小时前
如何用Python批量解压ZIP文件?快速解决方案
python
张槊哲8 小时前
ROS2架构介绍
python·架构
风逸hhh9 小时前
python打卡day29@浙大疏锦行
开发语言·前端·python
浩皓素9 小时前
深入理解For循环及相关关键字原理:以Python和C语言为例
c语言·python
英英_9 小时前
详细介绍一下Python连接MySQL数据库的完整步骤
数据库·python·mysql
水花花花花花9 小时前
GloVe 模型讲解与实战
python·深度学习·conda·pip